View Javadoc

1   package org.apache.turbine.services;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  
23  import java.util.ArrayList;
24  import java.util.Enumeration;
25  import java.util.Hashtable;
26  import java.util.Iterator;
27  
28  import org.apache.commons.configuration.BaseConfiguration;
29  import org.apache.commons.configuration.Configuration;
30  import org.apache.commons.lang.StringUtils;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  
34  /**
35   * A generic implementation of a <code>ServiceBroker</code> which
36   * provides:
37   *
38   * <ul>
39   * <li>Maintaining service name to class name mapping, allowing
40   * plugable service implementations.</li>
41   * <li>Providing <code>Services</code> with a configuration based on
42   * system wide configuration mechanism.</li>
43   * </ul>
44   * <li>Integration of TurbineServiceProviders for looking up
45   * non-local services
46   * </ul>
47   *
48   * @author <a href="mailto:burton@apache.org">Kevin Burton</a>
49   * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a>
50   * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
51   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
52   * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
53   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
54   * @version $Id: BaseServiceBroker.java 1066945 2011-02-03 20:27:59Z ludwig $
55   */
56  public abstract class BaseServiceBroker implements ServiceBroker
57  {
58      /**
59       * Mapping of Service names to class names.
60       */
61      private Configuration mapping = new BaseConfiguration();
62  
63      /**
64       * A repository of Service instances.
65       */
66      private Hashtable<String, Service> services = new Hashtable<String, Service>();
67  
68      /**
69       * Configuration for the services broker.
70       * The configuration should be set by the application
71       * in which the services framework is running.
72       */
73      private Configuration configuration;
74  
75      /**
76       * A prefix for <code>Service</code> properties in
77       * TurbineResource.properties.
78       */
79      public static final String SERVICE_PREFIX = "services.";
80  
81      /**
82       * A <code>Service</code> property determining its implementing
83       * class name .
84       */
85      public static final String CLASSNAME_SUFFIX = ".classname";
86  
87      /**
88       * These are objects that the parent application
89       * can provide so that application specific
90       * services have a mechanism to retrieve specialized
91       * information. For example, in Turbine there are services
92       * that require the RunData object: these services can
93       * retrieve the RunData object that Turbine has placed
94       * in the service manager. This alleviates us of
95       * the requirement of having init(Object) all
96       * together.
97       */
98      private Hashtable<String, Object> serviceObjects = new Hashtable<String, Object>();
99  
100     /** Logging */
101     private static Log log = LogFactory.getLog(BaseServiceBroker.class);
102 
103     /**
104      * Application root path as set by the
105      * parent application.
106      */
107     private String applicationRoot;
108 
109     /**
110      * mapping from service names to instances of TurbineServiceProviders
111      */
112     private Hashtable<String, Service> serviceProviderInstanceMap = new Hashtable<String, Service>();
113 
114     /**
115      * Default constructor, protected as to only be useable by subclasses.
116      *
117      * This constructor does nothing.
118      */
119     protected BaseServiceBroker()
120     {
121         // nothing to do
122     }
123 
124     /**
125      * Set the configuration object for the services broker.
126      * This is the configuration that contains information
127      * about all services in the care of this service
128      * manager.
129      *
130      * @param configuration Broker configuration.
131      */
132     public void setConfiguration(Configuration configuration)
133     {
134         this.configuration = configuration;
135     }
136 
137     /**
138      * Get the configuration for this service manager.
139      *
140      * @return Broker configuration.
141      */
142     public Configuration getConfiguration()
143     {
144         return configuration;
145     }
146 
147     /**
148      * Initialize this service manager.
149      */
150     public void init() throws InitializationException
151     {
152         // Check:
153         //
154         // 1. The configuration has been set.
155         // 2. Make sure the application root has been set.
156 
157         // FIXME: Make some service framework exceptions to throw in
158         // the event these requirements aren't satisfied.
159 
160         // Create the mapping between service names
161         // and their classes.
162         initMapping();
163 
164         // Start services that have their 'earlyInit'
165         // property set to 'true'.
166         initServices(false);
167     }
168 
169     /**
170      * Set an application specific service object
171      * that can be used by application specific
172      * services.
173      *
174      * @param name name of service object
175      * @param value value of service object
176      */
177     public void setServiceObject(String name, Object value)
178     {
179         serviceObjects.put(name, value);
180     }
181 
182     /**
183      * Get an application specific service object.
184      *
185      * @param name the name of the service object
186      * @return Object application specific service object
187      */
188     public Object getServiceObject(String name)
189     {
190         return serviceObjects.get(name);
191     }
192 
193     /**
194      * Creates a mapping between Service names and class names.
195      *
196      * The mapping is built according to settings present in
197      * TurbineResources.properties.  The entries should have the
198      * following form:
199      *
200      * <pre>
201      * services.MyService.classname=com.mycompany.MyServiceImpl
202      * services.MyOtherService.classname=com.mycompany.MyOtherServiceImpl
203      * </pre>
204      *
205      * <br>
206      *
207      * Generic ServiceBroker provides no Services.
208      */
209     @SuppressWarnings("unchecked")
210     protected void initMapping()
211     {
212         /*
213          * These keys returned in an order that corresponds
214          * to the order the services are listed in
215          * the TR.props.
216          *
217          * When the mapping is created we use a Configuration
218          * object to ensure that the we retain the order
219          * in which the order the keys are returned.
220          *
221          * There's no point in retrieving an ordered set
222          * of keys if they aren't kept in order :-)
223          */
224         for (Iterator<String> keys = configuration.getKeys(); keys.hasNext();)
225         {
226             String key = keys.next();
227             String[] keyParts = StringUtils.split(key, ".");
228 
229             if ((keyParts.length == 3)
230                     && (keyParts[0] + ".").equals(SERVICE_PREFIX)
231                     && ("." + keyParts[2]).equals(CLASSNAME_SUFFIX))
232             {
233                 String serviceKey = keyParts[1];
234                 log.info("Added Mapping for Service: " + serviceKey);
235 
236                 if (!mapping.containsKey(serviceKey))
237                 {
238                     mapping.setProperty(serviceKey,
239                             configuration.getString(key));
240                 }
241             }
242         }
243     }
244 
245     /**
246      * Determines whether a service is registered in the configured
247      * <code>TurbineResources.properties</code>.
248      *
249      * @param serviceName The name of the service whose existance to check.
250      * @return Registration predicate for the desired services.
251      */
252     public boolean isRegistered(String serviceName)
253     {
254         return (services.get(serviceName) != null);
255     }
256 
257     /**
258      * Returns an Iterator over all known service names.
259      *
260      * @return An Iterator of service names.
261      */
262     @SuppressWarnings("unchecked")
263     public Iterator<String> getServiceNames()
264     {
265         return mapping.getKeys();
266     }
267 
268     /**
269      * Returns an Iterator over all known service names beginning with
270      * the provided prefix.
271      *
272      * @param prefix The prefix against which to test.
273      * @return An Iterator of service names which match the prefix.
274      */
275     @SuppressWarnings("unchecked")
276     public Iterator<String> getServiceNames(String prefix)
277     {
278         return mapping.getKeys(prefix);
279     }
280 
281     /**
282      * Performs early initialization of specified service.
283      *
284      * @param name The name of the service (generally the
285      * <code>SERVICE_NAME</code> constant of the service's interface
286      * definition).
287      * @exception InitializationException Initialization of this
288      * service was not successful.
289      */
290     public synchronized void initService(String name)
291             throws InitializationException
292     {
293         // Calling getServiceInstance(name) assures that the Service
294         // implementation has its name and broker reference set before
295         // initialization.
296         Service instance = getServiceInstance(name);
297 
298         if (!instance.getInit())
299         {
300             // this call might result in an indirect recursion
301             instance.init();
302         }
303     }
304 
305     /**
306      * Performs early initialization of all services.  Failed early
307      * initialization of a Service may be non-fatal to the system,
308      * thus any exceptions are logged and the initialization process
309      * continues.
310      */
311     public void initServices()
312     {
313         try
314         {
315             initServices(false);
316         }
317         catch (InstantiationException notThrown)
318         {
319             log.debug("Caught non fatal exception", notThrown);
320         }
321         catch (InitializationException notThrown)
322         {
323             log.debug("Caught non fatal exception", notThrown);
324         }
325     }
326 
327     /**
328      * Performs early initialization of all services. You can decide
329      * to handle failed initializations if you wish, but then
330      * after one service fails, the other will not have the chance
331      * to initialize.
332      *
333      * @param report <code>true</code> if you want exceptions thrown.
334      */
335     public void initServices(boolean report)
336             throws InstantiationException, InitializationException
337     {
338         if (report)
339         {
340             // Throw exceptions
341             for (Iterator<String> names = getServiceNames(); names.hasNext();)
342             {
343                 doInitService(names.next());
344             }
345         }
346         else
347         {
348             // Eat exceptions
349             for (Iterator<String> names = getServiceNames(); names.hasNext();)
350             {
351                 try
352                 {
353                     doInitService(names.next());
354                 }
355                         // In case of an exception, file an error message; the
356                         // system may be still functional, though.
357                 catch (InstantiationException e)
358                 {
359                     log.error(e);
360                 }
361                 catch (InitializationException e)
362                 {
363                     log.error(e);
364                 }
365             }
366         }
367         log.info("Finished initializing all services!");
368     }
369 
370     /**
371      * Internal utility method for use in {@link #initServices(boolean)}
372      * to prevent duplication of code.
373      */
374     private void doInitService(String name)
375             throws InstantiationException, InitializationException
376     {
377         // Only start up services that have their earlyInit flag set.
378         if (getConfiguration(name).getBoolean("earlyInit", false))
379         {
380             log.info("Start Initializing service (early): " + name);
381             initService(name);
382             log.info("Finish Initializing service (early): " + name);
383         }
384     }
385 
386     /**
387      * Shuts down a <code>Service</code>, releasing resources
388      * allocated by an <code>Service</code>, and returns it to its
389      * initial (uninitialized) state.
390      *
391      * @param name The name of the <code>Service</code> to be
392      * uninitialized.
393      */
394     public synchronized void shutdownService(String name)
395     {
396         try
397         {
398             Service service = getServiceInstance(name);
399             if (service != null && service.getInit())
400             {
401                 service.shutdown();
402 
403                 if (service.getInit() && service instanceof BaseService)
404                 {
405                     // BaseService::shutdown() does this by default,
406                     // but could've been overriden poorly.
407                     ((BaseService) service).setInit(false);
408                 }
409             }
410         }
411         catch (InstantiationException e)
412         {
413             // Assuming harmless -- log the error and continue.
414             log.error("Shutdown of a nonexistent Service '"
415                     + name + "' was requested", e);
416         }
417     }
418 
419     /**
420      * Shuts down all Turbine services, releasing allocated resources and
421      * returning them to their initial (uninitialized) state.
422      */
423     public void shutdownServices()
424     {
425         log.info("Shutting down all services!");
426 
427         String serviceName = null;
428 
429         /*
430          * Now we want to reverse the order of
431          * this list. This functionality should be added to
432          * the ExtendedProperties in the commons but
433          * this will fix the problem for now.
434          */
435 
436         ArrayList<String> reverseServicesList = new ArrayList<String>();
437 
438         for (Iterator<String> serviceNames = getServiceNames(); serviceNames.hasNext();)
439         {
440             serviceName = serviceNames.next();
441             reverseServicesList.add(0, serviceName);
442         }
443 
444         for (Iterator<String> serviceNames = reverseServicesList.iterator(); serviceNames.hasNext();)
445         {
446             serviceName = serviceNames.next();
447             log.info("Shutting down service: " + serviceName);
448             shutdownService(serviceName);
449         }
450     }
451 
452     /**
453      * Returns an instance of requested Service.
454      *
455      * @param name The name of the Service requested.
456      * @return An instance of requested Service.
457      * @exception InstantiationException if the service is unknown or
458      * can't be initialized.
459      */
460     public Object getService(String name) throws InstantiationException
461     {
462         Service service;
463 
464         if (this.isLocalService(name))
465         {
466 	        try
467 	        {
468 	            service = getServiceInstance(name);
469 	            if (!service.getInit())
470 	            {
471 	                synchronized (service.getClass())
472 	                {
473 	                    if (!service.getInit())
474 	                    {
475 	                        log.info("Start Initializing service (late): " + name);
476 	                        service.init();
477 	                        log.info("Finish Initializing service (late): " + name);
478 	                    }
479 	                }
480 	            }
481 	            if (!service.getInit())
482 	            {
483 	                // this exception will be caught & rethrown by this very method.
484 	                // getInit() returning false indicates some initialization issue,
485 	                // which in turn prevents the InitableBroker from passing a
486 	                // reference to a working instance of the initable to the client.
487 	                throw new InitializationException(
488 	                        "init() failed to initialize service " + name);
489 	            }
490 	            return service;
491 	        }
492 	        catch (InitializationException e)
493 	        {
494 	            throw new InstantiationException("Service " + name +
495 	                    " failed to initialize", e);
496 	        }
497         }
498         else if (this.isNonLocalService(name))
499         {
500             return this.getNonLocalService(name);
501         }
502         else
503         {
504             throw new InstantiationException(
505                 "ServiceBroker: unknown service " + name
506                 + " requested");
507         }
508     }
509 
510     /**
511      * Retrieves an instance of a Service without triggering late
512      * initialization.
513      *
514      * Early initialization of a Service can require access to Service
515      * properties.  The Service must have its name and serviceBroker
516      * set by then.  Therefore, before calling
517      * Initable.initClass(Object), the class must be instantiated with
518      * InitableBroker.getInitableInstance(), and
519      * Service.setServiceBroker() and Service.setName() must be
520      * called.  This calls for two - level accessing the Services
521      * instances.
522      *
523      * @param name The name of the service requested.
524      * @exception InstantiationException The service is unknown or
525      * can't be initialized.
526      */
527     protected Service getServiceInstance(String name)
528             throws InstantiationException
529     {
530         Service service = services.get(name);
531 
532         if (service == null)
533         {
534 
535             String className=null;
536             if (!this.isLocalService(name))
537             {
538                 throw new InstantiationException(
539                         "ServiceBroker: unknown service " + name
540                         + " requested");
541             }
542             try
543             {
544                 className = mapping.getString(name);
545                 service = services.get(className);
546 
547                 if (service == null)
548                 {
549                     try
550                     {
551                         service = (Service) Class.forName(className).newInstance();
552 
553                         // check if the newly created service is also a
554                         // service provider - if so then remember it
555                         if (service instanceof TurbineServiceProvider)
556                         {
557                             this.serviceProviderInstanceMap.put(name,service);
558                         }
559 
560                     }
561                     // those two errors must be passed to the VM
562                     catch (ThreadDeath t)
563                     {
564                         throw t;
565                     }
566                     catch (OutOfMemoryError t)
567                     {
568                         throw t;
569                     }
570                     catch (Throwable t)
571                     {
572                         // Used to indicate error condition.
573                         String msg = null;
574 
575                         if (t instanceof NoClassDefFoundError)
576                         {
577                             msg = "A class referenced by " + className +
578                                     " is unavailable. Check your jars and classes.";
579                         }
580                         else if (t instanceof ClassNotFoundException)
581                         {
582                             msg = "Class " + className +
583                                     " is unavailable. Check your jars and classes.";
584                         }
585                         else if (t instanceof ClassCastException)
586                         {
587                             msg = "Class " + className +
588                                     " doesn't implement the Service interface";
589                         }
590                         else
591                         {
592                             msg = "Failed to instantiate " + className;
593                         }
594 
595                         throw new InstantiationException(msg, t);
596                     }
597                 }
598             }
599             catch (ClassCastException e)
600             {
601                 throw new InstantiationException("ServiceBroker: Class "
602                         + className
603                         + " does not implement Service interface.", e);
604             }
605             catch (InstantiationException e)
606             {
607                 throw new InstantiationException(
608                         "Failed to instantiate service " + name, e);
609             }
610             service.setServiceBroker(this);
611             service.setName(name);
612             services.put(name, service);
613         }
614 
615         return service;
616     }
617 
618     /**
619      * Returns the configuration for the specified service.
620      *
621      * @param name The name of the service.
622      * @return Configuration of requested Service.
623      */
624     public Configuration getConfiguration(String name)
625     {
626         return configuration.subset(SERVICE_PREFIX + name);
627     }
628 
629     /**
630      * Set the application root.
631      *
632      * @param applicationRoot application root
633      */
634     public void setApplicationRoot(String applicationRoot)
635     {
636         this.applicationRoot = applicationRoot;
637     }
638 
639     /**
640      * Get the application root as set by
641      * the parent application.
642      *
643      * @return String application root
644      */
645     public String getApplicationRoot()
646     {
647         return applicationRoot;
648     }
649 
650     /**
651      * Determines if the requested service is managed by this
652      * ServiceBroker.
653      *
654      * @param name The name of the Service requested.
655      * @return true if the service is managed by the this ServiceBroker
656      */
657     protected boolean isLocalService(String name)
658     {
659         return this.mapping.containsKey(name);
660     }
661 
662     /**
663      * Determines if the requested service is managed by an initialized
664      * TurbineServiceProvider. We use the service names to lookup
665      * the TurbineServiceProvider to ensure that we get a fully
666      * inititialized service.
667      *
668      * @param name The name of the Service requested.
669      * @return true if the service is managed by a TurbineServiceProvider
670      */
671     protected boolean isNonLocalService(String name)
672     {
673         String serviceName = null;
674         TurbineServiceProvider turbineServiceProvider = null;
675         Enumeration<String> list = this.serviceProviderInstanceMap.keys();
676 
677         while (list.hasMoreElements())
678         {
679             serviceName = list.nextElement();
680             turbineServiceProvider = (TurbineServiceProvider) this.getService(serviceName);
681 
682             if (turbineServiceProvider.exists(name))
683             {
684                 return true;
685             }
686         }
687 
688         return false;
689     }
690 
691     /**
692      * Get a non-local service managed by a TurbineServiceProvider.
693      *
694      * @param name The name of the Service requested.
695      * @return the requested service
696      * @throws InstantiationException the service couldn't be instantiated
697      */
698     protected Object getNonLocalService(String name)
699     	throws InstantiationException
700     {
701         String serviceName = null;
702         TurbineServiceProvider turbineServiceProvider = null;
703         Enumeration<String> list = this.serviceProviderInstanceMap.keys();
704 
705         while (list.hasMoreElements())
706         {
707             serviceName = list.nextElement();
708             turbineServiceProvider = (TurbineServiceProvider) this.getService(serviceName);
709 
710             if (turbineServiceProvider.exists(name))
711             {
712                 return turbineServiceProvider.get(name);
713             }
714         }
715 
716         throw new InstantiationException(
717             "ServiceBroker: unknown non-local service " + name
718             + " requested");
719     }
720 }