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