    001package org.apache.turbine.services;
    002
    003/*
    004 * Licensed to the Apache Software Foundation (ASF) under one
    005 * or more contributor license agreements.  See the NOTICE file
    006 * distributed with this work for additional information
    007 * regarding copyright ownership.  The ASF licenses this file
    008 * to you under the Apache License, Version 2.0 (the
    009 * "License"); you may not use this file except in compliance
    010 * with the License.  You may obtain a copy of the License at
    011 *
    012 *   http://www.apache.org/licenses/LICENSE-2.0
    013 *
    014 * Unless required by applicable law or agreed to in writing,
    015 * software distributed under the License is distributed on an
    016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    017 * KIND, either express or implied.  See the License for the
    018 * specific language governing permissions and limitations
    019 * under the License.
    020 */
    021
    022
    023import java.util.ArrayList;
    024import java.util.Enumeration;
    025import java.util.Hashtable;
    026import java.util.Iterator;
    027import java.util.LinkedHashMap;
    028import java.util.LinkedHashSet;
    029import java.util.Map;
    030import java.util.Set;
    031
    032import org.apache.commons.configuration.Configuration;
    033import org.apache.commons.lang.StringUtils;
    034import org.apache.commons.logging.Log;
    035import org.apache.commons.logging.LogFactory;
    036
    037/**
    038 * A generic implementation of a <code>ServiceBroker</code> which
    039 * provides:
    040 *
    041 * <ul>
    042 * <li>Maintaining service name to class name mapping, allowing
    043 * pluggable service implementations.</li>
    044 * <li>Providing <code>Services</code> with a configuration based on
    045 * system wide configuration mechanism.</li>
    046 * </ul>
    047 * <li>Integration of TurbineServiceProviders for looking up
    048 * non-local services
    049 * </ul>
    050 *
    051 * @author <a href="mailto:burton@apache.org">Kevin Burton</a>
    052 * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a>
    053 * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
    054 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
    055 * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
    056 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
    057 * @version $Id: BaseServiceBroker.java 1706239 2015-10-01 13:18:35Z tv $
    058 */
    059public abstract class BaseServiceBroker implements ServiceBroker
    060{
    061    /**
    062     * Mapping of Service names to class names, keep order.
    063     */
    064    private final Map<String, Class<?>> mapping = new LinkedHashMap<String, Class<?>>();
    065
    066    /**
    067     * A repository of Service instances.
    068     */
    069    private final Hashtable<String, Service> services = new Hashtable<String, Service>();
    070
    071    /**
    072     * Configuration for the services broker.
    073     * The configuration should be set by the application
    074     * in which the services framework is running.
    075     */
    076    private Configuration configuration;
    077
    078    /**
    079     * A prefix for <code>Service</code> properties in
    080     * TurbineResource.properties.
    081     */
    082    public static final String SERVICE_PREFIX = "services.";
    083
    084    /**
    085     * A <code>Service</code> property determining its implementing
    086     * class name .
    087     */
    088    public static final String CLASSNAME_SUFFIX = ".classname";
    089
    090    /**
    091     * These are objects that the parent application
    092     * can provide so that application specific
    093     * services have a mechanism to retrieve specialized
    094     * information. For example, in Turbine there are services
    095     * that require the RunData object: these services can
    096     * retrieve the RunData object that Turbine has placed
    097     * in the service manager. This alleviates us of
    098     * the requirement of having init(Object) all
    099     * 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}