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 }