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    }