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 }