001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.geronimo.osgi.registry; 018 019 import java.io.BufferedReader; 020 import java.io.IOException; 021 import java.io.InputStreamReader; 022 import java.net.URL; 023 import java.util.ArrayList; 024 import java.util.Enumeration; 025 import java.util.HashMap; 026 import java.util.LinkedHashSet; 027 import java.util.List; 028 import java.util.Map; 029 import java.util.Set; 030 031 import org.osgi.framework.Bundle; 032 import org.osgi.service.log.LogService; 033 034 /** 035 * The implementation of the provider registry used to store 036 * the bundle registrations. 037 */ 038 public class ProviderRegistryImpl implements org.apache.geronimo.osgi.registry.api.ProviderRegistry { 039 // indicates a bundle wishes to opt in to the META-INF/services registration and tracking. 040 public static final String OPT_IN_HEADER = "SPI-Provider"; 041 // provider classes exported via a header. 042 public static final String EXPORT_PROVIDER_HEADER = "Export-SPI-Provider"; 043 // our mapping between a provider id and the implementation information. There 044 // might be a one-to-many relationship between the ids and implementing classes. 045 private SPIRegistry providers = new SPIRegistry(); 046 // our mapping between an interface name and a META-INF/services SPI implementation. There 047 // might be a one-to-many relationship between the ids and implementing classes. 048 private SPIRegistry serviceProviders = new SPIRegistry(); 049 050 // our base Activator (used as a service source) 051 private Activator activator; 052 053 public ProviderRegistryImpl(Activator activator) { 054 this.activator = activator; 055 } 056 057 /** 058 * Add a bundle to the provider registry. This searches 059 * for services information in the OSGI-INF/providers 060 * directory of the bundle and registers this information 061 * in a provider registry. Bundles that need to locate 062 * class instances can use the provider registry to 063 * locate classes that might reside in other bundles. 064 * 065 * @param bundle The source bundle. 066 * 067 * @return A map of the located registrations. Returns null if 068 * this bundle does not contain any providers. 069 */ 070 public Object addBundle(Bundle bundle) { 071 log(LogService.LOG_DEBUG, "adding bundle " + bundle); 072 // create a tracker item for this bundle. This will record all of the information 073 // that's relevent to this bundle 074 BundleResources tracker = new BundleResources(bundle); 075 076 // if the tracker found information of interest, return it to the 077 // BundleTracker to let it know we need to watch this one. 078 return tracker.needsTracking() ? tracker : null; 079 } 080 081 082 /** 083 * Remove a bundle from the registry. 084 * 085 * @param bundle The target bundle. 086 */ 087 public void removeBundle(Bundle bundle, Object obj) { 088 log(LogService.LOG_DEBUG, "removing bundle " + bundle); 089 BundleResources tracker = (BundleResources)obj; 090 if (tracker != null) { 091 tracker.remove(); 092 } 093 } 094 095 096 /** 097 * Register an individual provivider item by its provider identifier. 098 * 099 * @param id The provider id. 100 * @param provider The loader used to resolve the provider class. 101 */ 102 protected void registerProvider(BundleProviderLoader provider) { 103 log(LogService.LOG_DEBUG, "registering provider " + provider); 104 providers.register(provider); 105 } 106 107 /** 108 * Removed a provider registration for a named provider id. 109 * 110 * @param id The target id 111 * @param provider The provider registration instance 112 */ 113 protected void unregisterProvider(BundleProviderLoader provider) { 114 log(LogService.LOG_DEBUG, "unregistering provider " + provider); 115 providers.unregister(provider); 116 } 117 118 119 /** 120 * Register an individual provivider item by its provider identifier. 121 * 122 * @param id The provider id. 123 * @param provider The loader used to resolve the provider class. 124 */ 125 protected void registerService(BundleProviderLoader provider) { 126 log(LogService.LOG_DEBUG, "registering service " + provider); 127 serviceProviders.register(provider); 128 } 129 130 /** 131 * Removed a provider registration for a named provider id. 132 * 133 * @param id The target id 134 * @param provider The provider registration instance 135 */ 136 protected void unregisterService(BundleProviderLoader provider) { 137 log(LogService.LOG_DEBUG, "unregistering service " + provider); 138 serviceProviders.unregister(provider); 139 } 140 141 142 /** 143 * Locate a class by its provider id indicator. . 144 * 145 * @param providerId The provider id (generally, a fully qualified class name). 146 * 147 * @return The Class corresponding to this provider id. Returns null 148 * if this is not registered or the indicated class can't be 149 * loaded. 150 */ 151 public Class<?> locate(String providerId) { 152 // see if we have a registered match for this...getting just the first instance 153 BundleProviderLoader loader = providers.getLoader(providerId); 154 if (loader != null) { 155 try { 156 // try to load this. We always return null 157 return loader.loadClass(); 158 } catch (Exception e) { 159 e.printStackTrace(); 160 // just swallow this and return null. The exception has already 161 // been logged. 162 } 163 } 164 // no match to return 165 return null; 166 } 167 168 /** 169 * Locate all class files that match a given provider id. 170 * 171 * @param providerId The target provider identifier. 172 * 173 * @return A List containing the class objects corresponding to the 174 * provider identifier. Returns an empty list if no 175 * matching classes can be located. 176 */ 177 public List<Class<?>> locateAll(String providerId) { 178 List<Class<?>> classes = new ArrayList<Class<?>>(); 179 List<BundleProviderLoader> l = providers.getLoaders(providerId); 180 // this returns null if nothing is found. 181 if (l != null) { 182 for (BundleProviderLoader c : l) { 183 try { 184 classes.add(c.loadClass()); 185 } catch (Exception e) { 186 // just swallow this and proceed to the next. The exception has 187 // already been logged. 188 } 189 } 190 } 191 return classes; 192 } 193 194 195 /** 196 * Locate and instantiate an instance of a service provider 197 * defined in the META-INF/services directory of tracked bundles. 198 * 199 * @param providerId The name of the target interface class. 200 * 201 * @return The service instance. Returns null if no service defintions 202 * can be located. 203 * @exception Exception Any classloading or other exceptions thrown during 204 * the process of creating this service instance. 205 */ 206 public Object getService(String providerId) throws Exception { 207 // see if we have a registered match for this...getting just the first instance 208 BundleProviderLoader loader = serviceProviders.getLoader(providerId); 209 if (loader != null) { 210 // try to load this and create an instance. Any/all exceptions get 211 // thrown here 212 return loader.createInstance(); 213 } 214 // no match to return 215 return null; 216 } 217 218 /** 219 * Locate all services that match a given provider id and create instances. 220 * 221 * @param providerId The target provider identifier. 222 * 223 * @return A List containing the instances corresponding to the 224 * provider identifier. Returns an empty list if no 225 * matching classes can be located or created 226 */ 227 public List<Object> getServices(String providerId) { 228 List<Object> instances = new ArrayList<Object>(); 229 List<BundleProviderLoader> l = serviceProviders.getLoaders(providerId); 230 // this returns null for nothing found 231 if (l != null) { 232 for (BundleProviderLoader c : l) { 233 try { 234 instances.add(c.createInstance()); 235 } catch (Exception e) { 236 // just swallow this and proceed to the next. The exception has 237 // already been logged. 238 } 239 } 240 } 241 return instances; 242 } 243 244 245 /** 246 * Locate all services that match a given provider id and return the implementation 247 * classes 248 * 249 * @param providerId The target provider identifier. 250 * 251 * @return A List containing the classes corresponding to the 252 * provider identifier. Returns an empty list if no 253 * matching classes can be located. 254 */ 255 public List<Class<?>> getServiceClasses(String providerId) { 256 List<Class<?>> classes = new ArrayList<Class<?>>(); 257 List<BundleProviderLoader> l = serviceProviders.getLoaders(providerId); 258 // this returns null for nothing found 259 if (l != null) { 260 for (BundleProviderLoader c : l) { 261 try { 262 classes.add(c.loadClass()); 263 } catch (Exception e) { 264 e.printStackTrace(); 265 // just swallow this and proceed to the next. The exception has 266 // already been logged. 267 } 268 } 269 } 270 return classes; 271 } 272 273 274 /** 275 * Locate and return the class for a service provider 276 * defined in the META-INF/services directory of tracked bundles. 277 * 278 * @param providerId The name of the target interface class. 279 * 280 * @return The provider class. Returns null if no service defintions 281 * can be located. 282 * @exception Exception Any classloading or other exceptions thrown during 283 * the process of loading this service provider class. 284 */ 285 public Class<?> getServiceClass(String providerId) throws ClassNotFoundException { 286 // see if we have a registered match for this...getting just the first instance 287 BundleProviderLoader loader = serviceProviders.getLoader(providerId); 288 if (loader != null) { 289 // try to load this and create an instance. Any/all exceptions get 290 // thrown here 291 return loader.loadClass(); 292 } 293 // no match to return 294 return null; 295 } 296 297 private void log(int level, String message) { 298 activator.log(level, message); 299 } 300 301 private void log(int level, String message, Throwable th) { 302 activator.log(level, message, th); 303 } 304 305 306 private class BundleResources { 307 // the bundle we're attached to. 308 private Bundle bundle; 309 // our map of providers maintained for the META-INF/services design pattern. 310 // this is an interface-to-provider instance mapping. 311 private List<BundleProviderLoader> serviceProviders; 312 // the defined mapping for provider classes...not maintained as an 313 // interface-to-provider mapping. 314 private List<BundleProviderLoader> providers; 315 316 public BundleResources(Bundle b) { 317 bundle = b; 318 // go locate any services we need 319 locateProviders(); 320 locateServices(); 321 } 322 323 public boolean needsTracking() { 324 return serviceProviders != null || providers != null; 325 } 326 327 // locate and process any providers defined in the OSGI-INF/providers directory 328 private void locateProviders() { 329 // we accumulate from the headers and the providers directory. The headers 330 // are simpler if there is no class mapping and is easier to use when 331 // converting a simple jar to a bundle. 332 Set<BundleProviderLoader> locatedProviders = new LinkedHashSet<BundleProviderLoader>(); 333 List<BundleProviderLoader> headerProviders = locateHeaderProviderDefinitions(); 334 if (headerProviders != null) { 335 locatedProviders.addAll(headerProviders); 336 } 337 338 List<BundleProviderLoader> directoryProviders = processDefinitions("OSGI-INF/providers/"); 339 if (directoryProviders != null) { 340 locatedProviders.addAll(directoryProviders); 341 } 342 // if we have anything, add to global registry 343 if (!locatedProviders.isEmpty()) { 344 // process the registrations for each item 345 for (BundleProviderLoader loader: locatedProviders) { 346 // add to the mapping table 347 registerProvider(loader); 348 } 349 // remember this list so we can unregister when the bundle is stopped 350 providers = new ArrayList(locatedProviders); 351 } 352 } 353 354 /** 355 * Parse the Export-Provider: header to create a list of 356 * providers that are exported via the header syntax 357 * rather than via a provider mapping file. 358 * 359 * @return A list of providers defined on the header, or null if 360 * no providers were exported. 361 */ 362 private List<BundleProviderLoader> locateHeaderProviderDefinitions() { 363 // check the header to see if there's anything defined here. 364 String exportedProviders = (String)bundle.getHeaders().get(EXPORT_PROVIDER_HEADER); 365 if (exportedProviders == null) { 366 return null; 367 } 368 369 List<BundleProviderLoader>providers = new ArrayList<BundleProviderLoader>(); 370 // split on the separator 371 String[] classNames = exportedProviders.split(","); 372 373 for (String name : classNames) { 374 name = name.trim(); 375 // this is a simple mapping 376 providers.add(new BundleProviderLoader(name, name, bundle)); 377 } 378 return providers; 379 } 380 381 // now process any services 382 private void locateServices() { 383 // we only process these if there is a header indicating this 384 // bundle wants to opt-in to this registration process. 385 if (bundle.getHeaders().get(OPT_IN_HEADER) == null) { 386 return; 387 } 388 389 log(LogService.LOG_INFO, OPT_IN_HEADER + " Manifest header found in bundle: " + bundle.getSymbolicName()); 390 391 serviceProviders = processDefinitions("META-INF/services/"); 392 // if we have anything, add to global registry 393 if (serviceProviders != null) { 394 // process the registrations for each item 395 for (BundleProviderLoader loader: serviceProviders) { 396 // add to the mapping table 397 registerService(loader); 398 } 399 } 400 } 401 402 403 /** 404 * Remove all resources associated with this bundle from the 405 * global registry. 406 */ 407 public void remove() { 408 log(LogService.LOG_DEBUG, "removing bundle " + bundle); 409 if (providers != null) { 410 for (BundleProviderLoader loader : providers) { 411 // unregistry the individual entry 412 unregisterProvider(loader); 413 } 414 } 415 416 if (serviceProviders != null) { 417 for (BundleProviderLoader loader : serviceProviders) { 418 // unregistry the individual entry 419 unregisterService(loader); 420 } 421 } 422 } 423 424 425 /** 426 * Process all of the service definition files in a given 427 * target path. This is used to process both the 428 * META-INF/services files and the OSGI-INF/providers files. 429 * 430 * @param path The target path location. 431 * 432 * @return The list of matching service definitions. Returns null if 433 * no matches were found. 434 */ 435 private List<BundleProviderLoader> processDefinitions(String path) { 436 List<BundleProviderLoader> mappings = new ArrayList<BundleProviderLoader>(); 437 438 // look for services definitions in the bundle...we accumulate these as provider class 439 // definitions. 440 Enumeration e = bundle.findEntries(path, "*", false); 441 if (e != null) { 442 while (e.hasMoreElements()) { 443 final URL u = (URL) e.nextElement(); 444 // go parse out the control file 445 parseServiceFile(u, mappings); 446 } 447 } 448 // only return this if we have something associated with this bundle 449 return mappings.isEmpty() ? null : mappings; 450 } 451 452 453 /** 454 * Parse a provider definition file and create loaders 455 * for all definitions contained within the file. 456 * 457 * @param u The URL of the file 458 * 459 * @return A list of the defined mappings. 460 */ 461 private void parseServiceFile(URL u, List<BundleProviderLoader>mappings) { 462 final String url = u.toString(); 463 // ignore directories 464 if (url.endsWith("/")) { 465 return; 466 } 467 468 // the identifier used for the provider is the last item in the URL. 469 final String providerId = url.substring(url.lastIndexOf("/") + 1); 470 try { 471 BufferedReader br = new BufferedReader(new InputStreamReader(u.openStream(), "UTF-8")); 472 String providerClassName = null; 473 // the file can be multiple lines long, with comments. A single file can define multiple providers 474 // for a single key, so we might need to create multiple entries. If the file does not contain any 475 // definition lines, then as a default, we use the providerId as an implementation class also. 476 String line = br.readLine(); 477 while (line != null) { 478 // we allow comments on these lines, and a line can be all comment 479 int comment = line.indexOf('#'); 480 if (comment != -1) { 481 line = line.substring(0, comment); 482 } 483 line = line.trim(); 484 // if there is nothing left on the line after stripping white space and comments, skip this 485 if (line.length() > 0) { 486 // add this to our list 487 mappings.add(new BundleProviderLoader(providerId, line, bundle)); 488 } 489 // keep reading until the end. 490 line = br.readLine(); 491 } 492 br.close(); 493 } catch (IOException e) { 494 // ignore errors and handle as default 495 } 496 } 497 } 498 499 500 /** 501 * Holder class for information about a given collection of 502 * id to provider mappings. Used for both the providers and 503 * the services. 504 */ 505 private class SPIRegistry { 506 private Map<String, List<BundleProviderLoader>> registry; 507 508 509 /** 510 * Register an individual provivider item by its provider identifier. 511 * 512 * @param id The provider id. 513 * @param provider The loader used to resolve the provider class. 514 */ 515 public synchronized void register(BundleProviderLoader provider) { 516 // if this is the first registration, create the mapping table 517 if (registry == null) { 518 registry = new HashMap<String, List<BundleProviderLoader>>(); 519 } 520 521 String providerId = provider.id(); 522 523 // the providers are stored as a list...we use the first one registered 524 // when asked to locate. 525 List<BundleProviderLoader> l = registry.get(providerId); 526 if (l == null) { 527 l = new ArrayList<BundleProviderLoader>(); 528 registry.put(providerId, l); 529 } 530 l.add(provider); 531 } 532 533 /** 534 * Remove a provider registration for a named provider id. 535 * 536 * @param provider The provider registration instance 537 */ 538 public synchronized void unregister(BundleProviderLoader provider) { 539 if (registry != null) { 540 // this is stored as a list. Just remove using the registration information 541 // This may move a different provider to the front of the list. 542 List<BundleProviderLoader> l = registry.get(provider.id()); 543 if (l != null) { 544 l.remove(provider); 545 } 546 } 547 } 548 549 550 private synchronized BundleProviderLoader getLoader(String id) { 551 // synchronize on the registry instance 552 if (registry != null) { 553 // return the first match, if any 554 List<BundleProviderLoader> list = registry.get(id); 555 if (list != null && !list.isEmpty()) { 556 return list.get(0); 557 } 558 } 559 // no match here 560 return null; 561 } 562 563 564 private synchronized List<BundleProviderLoader> getLoaders(String id) { 565 if (registry != null) { 566 // if we have matches, return a copy of what we currently have 567 // to create a safe local copy. 568 List<BundleProviderLoader> list = registry.get(id); 569 if (list != null && !list.isEmpty()) { 570 return new ArrayList<BundleProviderLoader>(list); 571 } 572 } 573 // no match here 574 return null; 575 } 576 } 577 578 579 /** 580 * Holder class for located services information. 581 */ 582 private class BundleProviderLoader { 583 // the class name for this provider 584 private final String providerId; 585 // the mapped class name of the provider. 586 private final String providerClass; 587 // the hosting bundle. 588 private final Bundle bundle; 589 590 /** 591 * Create a loader for this registered provider. 592 * 593 * @param providerId The provider ID 594 * @param providerClass The mapped class name of the provider. 595 * @param bundle The hosting bundle. 596 */ 597 public BundleProviderLoader(String providerId, String providerClass, Bundle bundle) { 598 this.providerId = providerId; 599 this.providerClass = providerClass; 600 this.bundle = bundle; 601 } 602 603 /** 604 * Load a provider class. 605 * 606 * @return The provider class from the target bundle. 607 * @exception Exception 608 */ 609 public Class<?> loadClass() throws ClassNotFoundException { 610 try { 611 log(LogService.LOG_DEBUG, "loading class for: " + this); 612 return bundle.loadClass(providerClass); 613 } catch (ClassNotFoundException e) { 614 log(LogService.LOG_DEBUG, "exception caught while loading " + this, e); 615 throw e; 616 } 617 } 618 619 /** 620 * Create an instance of the registred service. 621 * 622 * @return The created instance. A new instance is created on each call. 623 * @exception Exception 624 */ 625 public Object createInstance() throws Exception { 626 // get the class object 627 Class <?> cls = loadClass(); 628 try { 629 // just create an instance using the default constructor 630 return cls.newInstance(); 631 } catch (Exception e) { 632 log(LogService.LOG_DEBUG, "exception caught while creating " + this, e); 633 throw e; 634 } catch (Error e) { 635 log(LogService.LOG_DEBUG, "error caught while creating " + this, e); 636 throw e; 637 } 638 } 639 640 641 public String id() { 642 return providerId; 643 } 644 645 @Override 646 public String toString() { 647 return "Provider interface=" + providerId + " , provider class=" + providerClass + ", bundle=" + bundle; 648 } 649 650 @Override 651 public int hashCode() { 652 return providerId.hashCode() + providerClass.hashCode() + (int)bundle.getBundleId(); 653 } 654 655 @Override 656 public boolean equals(Object obj) { 657 if (obj instanceof BundleProviderLoader) { 658 return providerId.equals(((BundleProviderLoader)obj).providerId) && 659 providerClass.equals(((BundleProviderLoader)obj).providerClass) && 660 bundle.getBundleId() == ((BundleProviderLoader)obj).bundle.getBundleId(); 661 } else { 662 return false; 663 } 664 } 665 } 666 }