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