001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.geronimo.osgi.locator;
020
021 import java.io.BufferedReader;
022 import java.io.File;
023 import java.io.FileInputStream;
024 import java.io.IOException;
025 import java.io.InputStream;
026 import java.io.InputStreamReader;
027 import java.net.URL;
028 import java.util.ArrayList;
029 import java.util.Collection;
030 import java.util.Enumeration;
031 import java.util.LinkedHashSet;
032 import java.util.List;
033 import java.util.Properties;
034 import java.util.Set;
035
036 import org.apache.geronimo.osgi.registry.api.ProviderRegistry;
037 import org.osgi.framework.Bundle;
038 import org.osgi.framework.BundleContext;
039 import org.osgi.util.tracker.ServiceTracker;
040
041 public class ProviderLocator {
042 // our bundle context
043 static private BundleContext context;
044 // a service tracker for the registry service
045 // NB: This is declared as just Object to avoid classloading issues if we're running
046 // outside of an OSGi environment.
047 static private Object registryTracker;
048
049 private ProviderLocator() {
050 // private constructor to prevent an instance from getting created.
051 }
052
053 /**
054 * initialize the tracker statics for this bundle
055 *
056 * @param c The starup BundleContext.
057 */
058 public static void init(BundleContext c) {
059 try {
060 // just create a tracker for our lookup service
061 // NB: We use the hard coded name in case the registry service has not
062 // been started first. The ServiceTracker itself only uses the string name.
063 // We need to avoid trying to load the ProviderRegistry interface until the
064 // registry tracker returns a non-null service instance.
065 registryTracker = new ServiceTracker(c, "org.apache.geronimo.osgi.registry.api.ProviderRegistry", null);
066 ((ServiceTracker)registryTracker).open();
067 // do this last...it helps indicate if we have an initialized registry.
068 context = c;
069 } catch (Throwable e) {
070 // if there were any errors, then the registry is not available.
071 registryTracker = null;
072 }
073 }
074
075
076 /**
077 * Cleanup resources on bundle shutdown.
078 */
079 public static void destroy() {
080 if (registryTracker != null) {
081 // shutdown our tracking of the provider registry.
082 ((ServiceTracker)registryTracker).close();
083 registryTracker = null;
084 }
085 }
086
087
088 /**
089 * Locate a class by its provider id indicator. .
090 *
091 * @param providerId The provider id (generally, a fully qualified class name).
092 *
093 * @return The Class corresponding to this provider id. Returns null
094 * if this is not registered or the indicated class can't be
095 * loaded.
096 */
097 static public Class<?> locate(String providerId) {
098 Object registry = getRegistry();
099 // if no registry service available, this is a failure
100 if (registry == null) {
101 return null;
102 }
103 // get the service, if it exists. NB, if there is a service object,
104 // then the extender and the interface class are available, so this cast should be
105 // safe now.
106
107 // the rest of the work is done by the registry
108 return ((ProviderRegistry)registry).locate(providerId);
109 }
110
111 /**
112 * Locate all class files that match a given factory id.
113 *
114 * @param providerId The target provider identifier.
115 *
116 * @return A List containing the class objects corresponding to the
117 * provider identifier. Returns an empty list if no
118 * matching classes can be located.
119 */
120 static public List<Class<?>> locateAll(String providerId) {
121 Object registry = getRegistry();
122
123 // if no registry service available, this is a failure
124 if (registry == null) {
125 return new ArrayList<Class<?>>();
126 }
127 // get the service, if it exists. NB, if there is a service object,
128 // then the extender and the interface class are available, so this cast should be
129 // safe now.
130
131 // the rest of the work is done by the registry
132 return ((ProviderRegistry)registry).locateAll(providerId);
133 }
134
135 /**
136 * Utility class for locating a class with OSGi registry
137 * support. Uses the thread context classloader as part of
138 * the search order.
139 *
140 * @param className The name of the target class.
141 *
142 * @return The loaded class.
143 * @exception ClassNotFoundException
144 * Thrown if the class cannot be located.
145 */
146 static public Class<?> loadClass(String className) throws ClassNotFoundException {
147 return loadClass(className, null, Thread.currentThread().getContextClassLoader());
148 }
149
150 /**
151 * Utility class for locating a class with OSGi registry
152 * support. Uses the thread context classloader as part of
153 * the search order.
154 *
155 * @param className The name of the target class.
156 *
157 * @return The loaded class.
158 * @exception ClassNotFoundException
159 * Thrown if the class cannot be located.
160 */
161 static public Class<?> loadClass(String className, Class<?> contextClass) throws ClassNotFoundException {
162 return loadClass(className, contextClass, Thread.currentThread().getContextClassLoader());
163 }
164
165 /**
166 * Standardized utility method for performing class lookups
167 * with support for OSGi registry lookups.
168 *
169 * @param className The name of the target class.
170 * @param loader An optional class loader.
171 *
172 * @return The loaded class
173 * @exception ClassNotFoundException
174 * Thrown if the class cannot be loaded.
175 */
176 static public Class<?> loadClass(String className, Class<?>contextClass, ClassLoader loader) throws ClassNotFoundException {
177 // ideally, this should be last. However, some of the bundles duplicate classes
178 // found on the boot delegation, so we need to check this first to keep
179 // from picking up one of the default implementations.
180 Class cls = locate(className);
181 if (cls != null) {
182 return cls;
183 }
184
185 if (loader != null) {
186 try {
187 return loader.loadClass(className);
188 } catch (ClassNotFoundException x) {
189 }
190 }
191 if (contextClass != null) {
192 loader = contextClass.getClassLoader();
193 }
194 // try again using the class context loader
195 return Class.forName(className, true, loader);
196 }
197
198
199 /**
200 * Get a single service instance that matches an interface
201 * definition.
202 *
203 * @param iface The name of the required interface.
204 * @param contextClass
205 * The class requesting the lookup (used for class resolution).
206 * @param loader A class loader to use for searching for service definitions
207 * and loading classes.
208 *
209 * @return The service instance, or null if no matching services
210 * can be found.
211 * @exception Exception Thrown for any classloading or exceptions thrown
212 * trying to instantiate a service instance.
213 */
214 static public Object getService(String iface, Class<?> contextClass, ClassLoader loader) throws Exception {
215 // if we are working in an OSGi environment, then process the service
216 // registry first. Ideally, we would do this last, but because of boot delegation
217 // issues with some API implementations, we must try the OSGi version first
218 Object registry = getRegistry();
219 if (registry != null) {
220 // get the service, if it exists. NB, if there is a service object,
221 // then the extender and the interface class are available, so this cast should be
222 // safe now.
223 // the rest of the work is done by the registry
224 Object service = ((ProviderRegistry)registry).getService(iface);
225 if (service != null) {
226 return service;
227 }
228 }
229
230 // try for a classpath locatable instance next. If we find an appropriate class mapping,
231 // create an instance and return it.
232 Class<?> cls = locateServiceClass(iface, contextClass, loader);
233 if (cls != null) {
234 return cls.newInstance();
235 }
236 // a provider was not found
237 return null;
238 }
239
240
241 /**
242 * Locate a service class that matches an interface
243 * definition.
244 *
245 * @param iface The name of the required interface.
246 * @param contextClass
247 * The class requesting the lookup (used for class resolution).
248 * @param loader A class loader to use for searching for service definitions
249 * and loading classes.
250 *
251 * @return The located class, or null if no matching services
252 * can be found.
253 * @exception Exception Thrown for any classloading exceptions thrown
254 * trying to load the class.
255 */
256 static public Class<?> getServiceClass(String iface, Class<?> contextClass, ClassLoader loader) throws ClassNotFoundException {
257 // if we are working in an OSGi environment, then process the service
258 // registry first. Ideally, we would do this last, but because of boot delegation
259 // issues with some API implementations, we must try the OSGi version first
260 Object registry = getRegistry();
261 if (registry != null) {
262 // get the service, if it exists. NB, if there is a service object,
263 // then the extender and the interface class are available, so this cast should be
264 // safe now.
265
266 // If we've located stuff in the registry, then return it
267 Class<?> cls = ((ProviderRegistry)registry).getServiceClass(iface);
268 if (cls != null) {
269 return cls;
270 }
271 }
272
273 // try for a classpath locatable instance first. If we find an appropriate class mapping,
274 // create an instance and return it.
275 return locateServiceClass(iface, contextClass, loader);
276 }
277
278
279 /**
280 * Get a list of services that match a given interface
281 * name. This searches both the current class path and
282 * the global repository for matches.
283 *
284 * @param iface The name of the required interface.
285 * @param contextClass
286 * The class requesting the lookup (used for class resolution).
287 * @param loader A class loader to use for searching for service definitions
288 * and loading classes.
289 *
290 * @return A list of matching services. Returns an empty list if there
291 * are no matches.
292 * @exception Exception Thrown for any classloading or exceptions thrown
293 * trying to instantiate a service instance.
294 */
295 static public List<Object> getServices(String iface, Class<?> contextClass, ClassLoader loader) throws Exception {
296 List<Object> services = new ArrayList<Object>();
297
298 // because of boot delegation issues with some of the API implementations, it is necessary
299 // to process the OSGi registered versions first to allow override of JRE provided APIs.
300 Object registry = getRegistry();
301 if (registry != null) {
302 // get the service, if it exists. NB, if there is a service object,
303 // then the extender and the interface class are available, so this cast should be
304 // safe now.
305 // get any registered service instances now
306 List<Object> globalServices = ((ProviderRegistry)registry).getServices(iface);
307 // add to our list also
308 if (globalServices != null) {
309 services.addAll(globalServices);
310 }
311 }
312
313 // try for a classpath locatable instance second. If we find an appropriate class mapping,
314 // create an instance and return it.
315 Collection<Class<?>> classes = locateServiceClasses(iface, contextClass, loader);
316 if (classes != null) {
317 // create an instance of each of these classes
318 for (Class<?> cls : classes) {
319 services.add(cls.newInstance());
320 }
321 }
322
323 // now return the merged set
324 return services;
325 }
326
327
328 /**
329 * Get a list of service class implementations that match
330 * an interface name. This searches both the current class path and
331 * the global repository for matches.
332 *
333 * @param iface The name of the required interface.
334 * @param contextClass
335 * The class requesting the lookup (used for class resolution).
336 * @param loader A class loader to use for searching for service definitions
337 * and loading classes.
338 *
339 * @return A list of matching provider classes. Returns an empty list if there
340 * are no matches.
341 * @exception Exception Thrown for any classloading exceptions thrown
342 * trying to load a provider class.
343 */
344 static public List<Class<?>> getServiceClasses(String iface, Class<?> contextClass, ClassLoader loader) throws Exception {
345 Set<Class<?>> serviceClasses = new LinkedHashSet<Class<?>>();
346
347 // because of boot delegation issues with some of the API implementations, it is necessary
348 // to process the OSGi registered versions first to allow override of JRE provided APIs.
349 Object registry = getRegistry();
350 if (registry != null) {
351 // get the service, if it exists. NB, if there is a service object,
352 // then the extender and the interface class are available, so this cast should be
353 // safe now.
354 // get any registered service provider classes now
355 List<Class<?>> globalServices = ((ProviderRegistry)registry).getServiceClasses(iface);
356 // add to our list also
357 if (globalServices != null) {
358 serviceClasses.addAll(globalServices);
359 }
360 }
361
362 // try for a classpath locatable classes second. If we find an appropriate class mapping,
363 // add this to our return collection.
364 Collection<Class<?>> classes = locateServiceClasses(iface, contextClass, loader);
365 if (classes != null) {
366 serviceClasses.addAll(classes);
367 }
368 // now return the merged set
369 return new ArrayList(serviceClasses);
370 }
371
372
373 /**
374 * Locate the first class name for a META-INF/services definition
375 * of a given class. The first matching provider is
376 * returned.
377 *
378 * @param iface The interface class name used for the match.
379 * @param loader The classloader for locating resources.
380 *
381 * @return The mapped provider name, if found. Returns null if
382 * no mapping is located.
383 */
384 static private String locateServiceClassName(String iface, Class<?> contextClass, ClassLoader loader) {
385 // search first with the loader class path
386 String name = locateServiceClassName(iface, loader);
387 if (name != null) {
388 return name;
389 }
390 // then with the context class, if there is one
391 if (contextClass != null) {
392 name = locateServiceClassName(iface, contextClass.getClassLoader());
393 if (name != null) {
394 return name;
395 }
396 }
397 // not found
398 return null;
399 }
400
401
402 /**
403 * Locate a classpath-define service mapping.
404 *
405 * @param iface The required interface name.
406 * @param loader The ClassLoader instance to use to locate the service.
407 *
408 * @return The mapped class name, if one is found. Returns null if the
409 * mapping is not located.
410 */
411 static private String locateServiceClassName(String iface, ClassLoader loader) {
412 if (loader != null) {
413 try {
414 // we only look at resources that match the file name, using the specified loader
415 String service = "META-INF/services/" + iface;
416 Enumeration<URL> providers = loader.getResources(service);
417
418 while (providers.hasMoreElements()) {
419 List<String>providerNames = parseServiceDefinition(providers.nextElement());
420 // if there is something defined here, return the first entry
421 if (!providerNames.isEmpty()) {
422 return providerNames.get(0);
423 }
424 }
425 } catch (IOException e) {
426 }
427 }
428 // not found
429 return null;
430 }
431
432
433 /**
434 * Locate the first class for a META-INF/services definition
435 * of a given interface class. The first matching provider is
436 * returned.
437 *
438 * @param iface The interface class name used for the match.
439 * @param loader The classloader for locating resources.
440 *
441 * @return The mapped provider class, if found. Returns null if
442 * no mapping is located.
443 */
444 static private Class<?> locateServiceClass(String iface, Class<?> contextClass, ClassLoader loader) throws ClassNotFoundException {
445 String className = locateServiceClassName(iface, contextClass, loader);
446 if (className == null) {
447 return null;
448 }
449
450 // we found a name, try loading the class. This will throw an exception if there is an error
451 return loadClass(className, contextClass, loader);
452 }
453
454
455 /**
456 * Locate all class names name for a META-INF/services definition
457 * of a given class.
458 *
459 * @param iface The interface class name used for the match.
460 * @param loader The classloader for locating resources.
461 *
462 * @return The mapped provider name, if found. Returns null if
463 * no mapping is located.
464 */
465 static private Collection<String> locateServiceClassNames(String iface, Class<?> contextClass, ClassLoader loader) {
466 Set<String> names = new LinkedHashSet<String>();
467
468 locateServiceClassNames(iface, loader, names);
469 if (contextClass != null) {
470 locateServiceClassNames(iface, contextClass.getClassLoader(), names);
471 }
472
473 return names;
474 }
475
476
477 /**
478 * Locate all class names name for a META-INF/services definition
479 * of a given class.
480 *
481 * @param iface The interface class name used for the match.
482 * @param loader The classloader for locating resources.
483 *
484 * @return The mapped provider name, if found. Returns null if
485 * no mapping is located.
486 */
487 static void locateServiceClassNames(String iface, ClassLoader loader, Set names) {
488 if (loader != null) {
489
490 try {
491 // we only look at resources that match the file name, using the specified loader
492 String service = "META-INF/services/" + iface;
493 Enumeration<URL> providers = loader.getResources(service);
494
495 while (providers.hasMoreElements()) {
496 List<String>providerNames = parseServiceDefinition(providers.nextElement());
497 // just add all of these to the list
498 names.addAll(providerNames);
499 }
500 } catch (IOException e) {
501 }
502 }
503 }
504
505
506 /**
507 * Locate all classes that map to a given provider class definition. This will
508 * search both the services directories, as well as the provider classes from the
509 * OSGi provider registry.
510 *
511 * @param iface The interface class name used for the match.
512 * @param loader The classloader for locating resources.
513 *
514 * @return A list of all mapped classes, if found. Returns an empty list if
515 * no mappings are found.
516 */
517 static private Collection<Class<?>> locateServiceClasses(String iface, Class<?> contextClass, ClassLoader loader) throws ClassNotFoundException {
518 // get the set of names from services definitions on the classpath
519 Collection<String> classNames = locateServiceClassNames(iface, contextClass, loader);
520 Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
521
522 // load each class and add to our return set
523 for (String name : classNames) {
524 classes.add(loadClass(name, contextClass, loader));
525 }
526 return classes;
527 }
528
529
530 /**
531 * Parse a definition file and return the names of all included implementation classes
532 * contained within the file.
533 *
534 * @param u The URL of the file
535 *
536 * @return A list of all matching classes. Returns an empty list
537 * if no matches are found.
538 */
539 static private List<String> parseServiceDefinition(URL u) {
540 final String url = u.toString();
541 List<String> classes = new ArrayList<String>();
542 // ignore directories
543 if (url.endsWith("/")) {
544 return classes;
545 }
546 // the identifier used for the provider is the last item in the URL.
547 final String providerId = url.substring(url.lastIndexOf("/") + 1);
548 try {
549 BufferedReader br = new BufferedReader(new InputStreamReader(u.openStream(), "UTF-8"));
550 // the file can be multiple lines long, with comments. A single file can define multiple providers
551 // for a single key, so we might need to create multiple entries. If the file does not contain any
552 // definition lines, then as a default, we use the providerId as an implementation class also.
553 String line = br.readLine();
554 while (line != null) {
555 // we allow comments on these lines, and a line can be all comment
556 int comment = line.indexOf('#');
557 if (comment != -1) {
558 line = line.substring(0, comment);
559 }
560 line = line.trim();
561 // if there is nothing left on the line after stripping white space and comments, skip this
562 if (line.length() > 0) {
563 // add this to our list
564 classes.add(line);
565 }
566 // keep reading until the end.
567 line = br.readLine();
568 }
569 br.close();
570 } catch (IOException e) {
571 // ignore errors and handle as default
572 }
573 return classes;
574 }
575
576 /**
577 * Perform a service class discovery by looking for a
578 * property in a target properties file located in the
579 * java.home directory.
580 *
581 * @param path The relative path to the desired properties file.
582 * @param property The name of the required property.
583 *
584 * @return The value of the named property within the properties file. Returns
585 * null if the property doesn't exist or the properties file doesn't exist.
586 */
587 public static String lookupByJREPropertyFile(String path, String property) throws IOException {
588 String jreDirectory = System.getProperty("java.home");
589 File configurationFile = new File(jreDirectory + File.separator + path);
590 if (configurationFile.exists() && configurationFile.canRead()) {
591 Properties properties = new Properties();
592 InputStream in = null;
593 try {
594 in = new FileInputStream(configurationFile);
595 properties.load(in);
596 return properties.getProperty(property);
597 } finally {
598 if (in != null) {
599 try {
600 in.close();
601 } catch (Exception e) {
602 }
603 }
604 }
605 }
606 return null;
607 }
608
609
610 /**
611 * Retrieve the registry from the tracker if it is available,
612 * all without causing the interface class to load.
613 *
614 * @return The registry service instance, or null if it is not
615 * available for any reason.
616 */
617 private static Object getRegistry() {
618 // if not initialized in an OSGi environment, this is a failure
619 if (registryTracker == null) {
620 return null;
621 }
622 // get the service, if it exists. NB: it is only safe to reference the
623 // interface class if the tracker returns a non-null service object. The
624 // interface class will not be loaded in our bundle context until the
625 // service class can be statisfied. Therefore, we always return this as
626 // just an object and the call needs to perform the cast, which will
627 // force the classload at that time.
628 return ((ServiceTracker)registryTracker).getService();
629 }
630 }