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    }