    001    package 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
    023    import java.util.ArrayList;
    024    import java.util.Enumeration;
    025    import java.util.Hashtable;
    026    import java.util.Iterator;
    027
    028    import org.apache.commons.configuration.BaseConfiguration;
    029    import org.apache.commons.configuration.Configuration;
    030    import org.apache.commons.lang.StringUtils;
    031    import org.apache.commons.logging.Log;
    032    import org.apache.commons.logging.LogFactory;
    033
    034    /**
    035     * A generic implementation of a <code>ServiceBroker</code> which
    036     * provides:
    037     *
    038     * <ul>
    039     * <li>Maintaining service name to class name mapping, allowing
    040     * plugable service implementations.</li>
    041     * <li>Providing <code>Services</code> with a configuration based on
    042     * system wide configuration mechanism.</li>
    043     * </ul>
    044     * <li>Integration of TurbineServiceProviders for looking up
    045     * non-local services
    046     * </ul>
    047     *
    048     * @author <a href="mailto:burton@apache.org">Kevin Burton</a>
    049     * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a>
    050     * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
    051     * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
    052     * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
    053     * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
    054     * @version $Id: BaseServiceBroker.java 1066945 2011-02-03 20:27:59Z ludwig $
    055     */
    056    public abstract class BaseServiceBroker implements ServiceBroker
    057    {
    058        /**
    059         * Mapping of Service names to class names.
    060         */
    061        private Configuration mapping = new BaseConfiguration();
    062
    063        /**
    064         * A repository of Service instances.
    065         */
    066        private Hashtable<String, Service> services = new Hashtable<String, Service>();
    067
    068        /**
    069         * Configuration for the services broker.
    070         * The configuration should be set by the application
    071         * in which the services framework is running.
    072         */
    073        private Configuration configuration;
    074
    075        /**
    076         * A prefix for <code>Service</code> properties in
    077         * TurbineResource.properties.
    078         */
    079        public static final String SERVICE_PREFIX = "services.";
    080
    081        /**
    082         * A <code>Service</code> property determining its implementing
    083         * class name .
    084         */
    085        public static final String CLASSNAME_SUFFIX = ".classname";
    086
    087        /**
    088         * These are objects that the parent application
    089         * can provide so that application specific
    090         * services have a mechanism to retrieve specialized
    091         * information. For example, in Turbine there are services
    092         * that require the RunData object: these services can
    093         * retrieve the RunData object that Turbine has placed
    094         * in the service manager. This alleviates us of
    095         * the requirement of having init(Object) all
    096         * together.
    097         */
    098        private Hashtable<String, Object> serviceObjects = new Hashtable<String, Object>();
    099
    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    }