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 }