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}