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 }