    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.Iterator;
    025import java.util.LinkedHashMap;
    026import java.util.LinkedHashSet;
    027import java.util.Map;
    028import java.util.Set;
    029import java.util.concurrent.ConcurrentHashMap;
    030import java.util.concurrent.locks.ReentrantLock;
    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 * <li>Integration of TurbineServiceProviders for looking up
    047 * non-local services</li>
    048 * </ul>
    049 *
    050 * @author <a href="mailto:burton@apache.org">Kevin Burton</a>
    051 * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a>
    052 * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
    053 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
    054 * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
    055 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
    056 * @version $Id: BaseServiceBroker.java 1820269 2018-01-05 08:33:52Z gk $
    057 */
    058public abstract class BaseServiceBroker implements ServiceBroker
    059{
    060    /**
    061     * Mapping of Service names to class names, keep order.
    062     */
    063    private final Map<String, Class<?>> mapping = new LinkedHashMap<String, Class<?>>();
    064
    065    /**
    066     * A repository of Service instances.
    067     */
    068    private final ConcurrentHashMap<String, Service> services = new ConcurrentHashMap<String, Service>();
    069
    070    /**
    071     * Lock access during service initialization
    072     */
    073    private final ReentrantLock serviceLock = new ReentrantLock();
    074
    075    /**
    076     * Configuration for the services broker.
    077     * The configuration should be set by the application
    078     * in which the services framework is running.
    079     */
    080    private Configuration configuration;
    081
    082    /**
    083     * A prefix for <code>Service</code> properties in
    084     * TurbineResource.properties.
    085     */
    086    public static final String SERVICE_PREFIX = "services.";
    087
    088    /**
    089     * A <code>Service</code> property determining its implementing
    090     * class name .
    091     */
    092    public static final String CLASSNAME_SUFFIX = ".classname";
    093
    094    /**
    095     * These are objects that the parent application
    096     * can provide so that application specific
    097     * services have a mechanism to retrieve specialized
    098     * information. For example, in Turbine there are services
    099     * 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}