View Javadoc

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