View Javadoc

1   package org.apache.turbine.services;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.ArrayList;
23  import java.util.Hashtable;
24  import java.util.Iterator;
25  
26  import org.apache.commons.configuration.BaseConfiguration;
27  import org.apache.commons.configuration.Configuration;
28  import org.apache.commons.lang.StringUtils;
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  
32  /***
33   * A generic implementation of a <code>ServiceBroker</code> which
34   * provides:
35   *
36   * <ul>
37   * <li>Maintaining service name to class name mapping, allowing
38   * plugable service implementations.</li>
39   * <li>Providing <code>Services</code> with a configuration based on
40   * system wide configuration mechanism.</li>
41   * </ul>
42   *
43   * @author <a href="mailto:burton@apache.org">Kevin Burton</a>
44   * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a>
45   * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
46   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
47   * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
48   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
49   * @version $Id: BaseServiceBroker.java 534527 2007-05-02 16:10:59Z tv $
50   */
51  public abstract class BaseServiceBroker implements ServiceBroker
52  {
53      /***
54       * Mapping of Service names to class names.
55       */
56      protected Configuration mapping = new BaseConfiguration();
57  
58      /***
59       * A repository of Service instances.
60       */
61      protected Hashtable services = new Hashtable();
62  
63      /***
64       * Configuration for the services broker.
65       * The configuration should be set by the application
66       * in which the services framework is running.
67       */
68      protected Configuration configuration;
69  
70      /***
71       * A prefix for <code>Service</code> properties in
72       * TurbineResource.properties.
73       */
74      public static final String SERVICE_PREFIX = "services.";
75  
76      /***
77       * A <code>Service</code> property determining its implementing
78       * class name .
79       */
80      public static final String CLASSNAME_SUFFIX = ".classname";
81  
82      /***
83       * These are objects that the parent application
84       * can provide so that application specific
85       * services have a mechanism to retrieve specialized
86       * information. For example, in Turbine there are services
87       * that require the RunData object: these services can
88       * retrieve the RunData object that Turbine has placed
89       * in the service manager. This alleviates us of
90       * the requirement of having init(Object) all
91       * together.
92       */
93      protected Hashtable serviceObjects = new Hashtable();
94  
95      /*** Logging */
96      private static Log log = LogFactory.getLog(BaseServiceBroker.class);
97  
98      /***
99       * Application root path as set by the
100      * parent application.
101      */
102     protected String applicationRoot;
103 
104     /***
105      * Default constructor, protected as to only be useable by subclasses.
106      *
107      * This constructor does nothing.
108      */
109     protected BaseServiceBroker()
110     {
111     }
112 
113     /***
114      * Set the configuration object for the services broker.
115      * This is the configuration that contains information
116      * about all services in the care of this service
117      * manager.
118      *
119      * @param configuration Broker configuration.
120      */
121     public void setConfiguration(Configuration configuration)
122     {
123         this.configuration = configuration;
124     }
125 
126     /***
127      * Get the configuration for this service manager.
128      *
129      * @return Broker configuration.
130      */
131     public Configuration getConfiguration()
132     {
133         return configuration;
134     }
135 
136     /***
137      * Initialize this service manager.
138      */
139     public void init() throws InitializationException
140     {
141         // Check:
142         //
143         // 1. The configuration has been set.
144         // 2. Make sure the application root has been set.
145 
146         // FIXME: Make some service framework exceptions to throw in
147         // the event these requirements aren't satisfied.
148 
149         // Create the mapping between service names
150         // and their classes.
151         initMapping();
152 
153         // Start services that have their 'earlyInit'
154         // property set to 'true'.
155         initServices(false);
156     }
157 
158     /***
159      * Set an application specific service object
160      * that can be used by application specific
161      * services.
162      *
163      * @param name name of service object
164      * @param value value of service object
165      */
166     public void setServiceObject(String name, Object value)
167     {
168         serviceObjects.put(name, value);
169     }
170 
171     /***
172      * Get an application specific service object.
173      *
174      * @return Object application specific service object
175      */
176     public Object getServiceObject(String name)
177     {
178         return serviceObjects.get(name);
179     }
180 
181     /***
182      * Creates a mapping between Service names and class names.
183      *
184      * The mapping is built according to settings present in
185      * TurbineResources.properties.  The entries should have the
186      * following form:
187      *
188      * <pre>
189      * services.MyService.classname=com.mycompany.MyServiceImpl
190      * services.MyOtherService.classname=com.mycompany.MyOtherServiceImpl
191      * </pre>
192      *
193      * <br>
194      *
195      * Generic ServiceBroker provides no Services.
196      */
197     protected void initMapping()
198     {
199         /*
200          * These keys returned in an order that corresponds
201          * to the order the services are listed in
202          * the TR.props.
203          *
204          * When the mapping is created we use a Configuration
205          * object to ensure that the we retain the order
206          * in which the order the keys are returned.
207          *
208          * There's no point in retrieving an ordered set
209          * of keys if they aren't kept in order :-)
210          */
211         for (Iterator keys = configuration.getKeys(); keys.hasNext();)
212         {
213             String key = (String) keys.next();
214             String[] keyParts = StringUtils.split(key, ".");
215 
216             if ((keyParts.length == 3)
217                     && (keyParts[0] + ".").equals(SERVICE_PREFIX)
218                     && ("." + keyParts[2]).equals(CLASSNAME_SUFFIX))
219             {
220                 String serviceKey = keyParts[1];
221                 log.info("Added Mapping for Service: " + serviceKey);
222 
223                 if (!mapping.containsKey(serviceKey))
224                 {
225                     mapping.setProperty(serviceKey,
226                             configuration.getString(key));
227                 }
228             }
229         }
230     }
231 
232     /***
233      * Determines whether a service is registered in the configured
234      * <code>TurbineResources.properties</code>.
235      *
236      * @param serviceName The name of the service whose existance to check.
237      * @return Registration predicate for the desired services.
238      */
239     public boolean isRegistered(String serviceName)
240     {
241         return (services.get(serviceName) != null);
242     }
243 
244     /***
245      * Returns an Iterator over all known service names.
246      *
247      * @return An Iterator of service names.
248      */
249     public Iterator getServiceNames()
250     {
251         return mapping.getKeys();
252     }
253 
254     /***
255      * Returns an Iterator over all known service names beginning with
256      * the provided prefix.
257      *
258      * @param prefix The prefix against which to test.
259      * @return An Iterator of service names which match the prefix.
260      */
261     public Iterator getServiceNames(String prefix)
262     {
263         return mapping.getKeys(prefix);
264     }
265 
266     /***
267      * Performs early initialization of specified service.
268      *
269      * @param name The name of the service (generally the
270      * <code>SERVICE_NAME</code> constant of the service's interface
271      * definition).
272      * @exception InitializationException Initialization of this
273      * service was not successful.
274      */
275     public synchronized void initService(String name)
276             throws InitializationException
277     {
278         // Calling getServiceInstance(name) assures that the Service
279         // implementation has its name and broker reference set before
280         // initialization.
281         Service instance = getServiceInstance(name);
282 
283         if (!instance.getInit())
284         {
285             // this call might result in an indirect recursion
286             instance.init();
287         }
288     }
289 
290     /***
291      * Performs early initialization of all services.  Failed early
292      * initialization of a Service may be non-fatal to the system,
293      * thus any exceptions are logged and the initialization process
294      * continues.
295      */
296     public void initServices()
297     {
298         try
299         {
300             initServices(false);
301         }
302         catch (InstantiationException notThrown)
303         {
304             log.debug("Caught non fatal exception", notThrown);
305         }
306         catch (InitializationException notThrown)
307         {
308             log.debug("Caught non fatal exception", notThrown);
309         }
310     }
311 
312     /***
313      * Performs early initialization of all services. You can decide
314      * to handle failed initializations if you wish, but then
315      * after one service fails, the other will not have the chance
316      * to initialize.
317      *
318      * @param report <code>true</code> if you want exceptions thrown.
319      */
320     public void initServices(boolean report)
321             throws InstantiationException, InitializationException
322     {
323         if (report)
324         {
325             // Throw exceptions
326             for (Iterator names = getServiceNames(); names.hasNext();)
327             {
328                 doInitService((String) names.next());
329             }
330         }
331         else
332         {
333             // Eat exceptions
334             for (Iterator names = getServiceNames(); names.hasNext();)
335             {
336                 try
337                 {
338                     doInitService((String) names.next());
339                 }
340                         // In case of an exception, file an error message; the
341                         // system may be still functional, though.
342                 catch (InstantiationException e)
343                 {
344                     log.error(e);
345                 }
346                 catch (InitializationException e)
347                 {
348                     log.error(e);
349                 }
350             }
351         }
352         log.info("Finished initializing all services!");
353     }
354 
355     /***
356      * Internal utility method for use in {@link #initServices(boolean)}
357      * to prevent duplication of code.
358      */
359     private void doInitService(String name)
360             throws InstantiationException, InitializationException
361     {
362         // Only start up services that have their earlyInit flag set.
363         if (getConfiguration(name).getBoolean("earlyInit", false))
364         {
365             log.info("Start Initializing service (early): " + name);
366             initService(name);
367             log.info("Finish Initializing service (early): " + name);
368         }
369     }
370 
371     /***
372      * Shuts down a <code>Service</code>, releasing resources
373      * allocated by an <code>Service</code>, and returns it to its
374      * initial (uninitialized) state.
375      *
376      * @param name The name of the <code>Service</code> to be
377      * uninitialized.
378      */
379     public synchronized void shutdownService(String name)
380     {
381         try
382         {
383             Service service = getServiceInstance(name);
384             if (service != null && service.getInit())
385             {
386                 service.shutdown();
387                 if (service.getInit() && service instanceof BaseService)
388                 {
389                     // BaseService::shutdown() does this by default,
390                     // but could've been overriden poorly.
391                     ((BaseService) service).setInit(false);
392                 }
393             }
394         }
395         catch (InstantiationException e)
396         {
397             // Assuming harmless -- log the error and continue.
398             log.error("Shutdown of a nonexistent Service '"
399                     + name + "' was requested", e);
400         }
401     }
402 
403     /***
404      * Shuts down all Turbine services, releasing allocated resources and
405      * returning them to their initial (uninitialized) state.
406      */
407     public void shutdownServices()
408     {
409         log.info("Shutting down all services!");
410 
411         String serviceName = null;
412 
413         /*
414          * Now we want to reverse the order of
415          * this list. This functionality should be added to
416          * the ExtendedProperties in the commons but
417          * this will fix the problem for now.
418          */
419 
420         ArrayList reverseServicesList = new ArrayList();
421 
422         for (Iterator serviceNames = getServiceNames(); serviceNames.hasNext();)
423         {
424             serviceName = (String) serviceNames.next();
425             reverseServicesList.add(0, serviceName);
426         }
427 
428         for (Iterator serviceNames = reverseServicesList.iterator(); serviceNames.hasNext();)
429         {
430             serviceName = (String) serviceNames.next();
431             log.info("Shutting down service: " + serviceName);
432             shutdownService(serviceName);
433         }
434     }
435 
436     /***
437      * Returns an instance of requested Service.
438      *
439      * @param name The name of the Service requested.
440      * @return An instance of requested Service.
441      * @exception InstantiationException if the service is unknown or
442      * can't be initialized.
443      */
444     public Service getService(String name) throws InstantiationException
445     {
446         Service service;
447         try
448         {
449             service = getServiceInstance(name);
450             if (!service.getInit())
451             {
452                 synchronized (service.getClass())
453                 {
454                     if (!service.getInit())
455                     {
456                         log.info("Start Initializing service (late): " + name);
457                         service.init();
458                         log.info("Finish Initializing service (late): " + name);
459                     }
460                 }
461             }
462             if (!service.getInit())
463             {
464                 // this exception will be caught & rethrown by this very method.
465                 // getInit() returning false indicates some initialization issue,
466                 // which in turn prevents the InitableBroker from passing a
467                 // reference to a working instance of the initable to the client.
468                 throw new InitializationException(
469                         "init() failed to initialize service " + name);
470             }
471             return service;
472         }
473         catch (InitializationException e)
474         {
475             throw new InstantiationException("Service " + name +
476                     " failed to initialize", e);
477         }
478     }
479 
480     /***
481      * Retrieves an instance of a Service without triggering late
482      * initialization.
483      *
484      * Early initialization of a Service can require access to Service
485      * properties.  The Service must have its name and serviceBroker
486      * set by then.  Therefore, before calling
487      * Initable.initClass(Object), the class must be instantiated with
488      * InitableBroker.getInitableInstance(), and
489      * Service.setServiceBroker() and Service.setName() must be
490      * called.  This calls for two - level accessing the Services
491      * instances.
492      *
493      * @param name The name of the service requested.
494      * @exception InstantiationException The service is unknown or
495      * can't be initialized.
496      */
497     protected Service getServiceInstance(String name)
498             throws InstantiationException
499     {
500         Service service = (Service) services.get(name);
501 
502         if (service == null)
503         {
504             String className = mapping.getString(name);
505             if (StringUtils.isEmpty(className))
506             {
507                 throw new InstantiationException(
508                         "ServiceBroker: unknown service " + name
509                         + " requested");
510             }
511             try
512             {
513                 service = (Service) services.get(className);
514 
515                 if (service == null)
516                 {
517                     try
518                     {
519                         service = (Service)
520                                 Class.forName(className).newInstance();
521                     }
522                     // those two errors must be passed to the VM
523                     catch (ThreadDeath t)
524                     {
525                         throw t;
526                     }
527                     catch (OutOfMemoryError t)
528                     {
529                         throw t;
530                     }
531                     catch (Throwable t)
532                     {
533                         // Used to indicate error condition.
534                         String msg = null;
535 
536                         if (t instanceof NoClassDefFoundError)
537                         {
538                             msg = "A class referenced by " + className +
539                                     " is unavailable. Check your jars and classes.";
540                         }
541                         else if (t instanceof ClassNotFoundException)
542                         {
543                             msg = "Class " + className +
544                                     " is unavailable. Check your jars and classes.";
545                         }
546                         else if (t instanceof ClassCastException)
547                         {
548                             msg = "Class " + className +
549                                     " doesn't implement the Service interface";
550                         }
551                         else
552                         {
553                             msg = "Failed to instantiate " + className;
554                         }
555 
556                         throw new InstantiationException(msg, t);
557                     }
558                 }
559             }
560             catch (ClassCastException e)
561             {
562                 throw new InstantiationException("ServiceBroker: Class "
563                         + className
564                         + " does not implement Service interface.", e);
565             }
566             catch (InstantiationException e)
567             {
568                 throw new InstantiationException(
569                         "Failed to instantiate service " + name, e);
570             }
571             service.setServiceBroker(this);
572             service.setName(name);
573             services.put(name, service);
574         }
575 
576         return service;
577     }
578 
579     /***
580      * Returns the configuration for the specified service.
581      *
582      * @param name The name of the service.
583      * @return Configuration of requested Service.
584      */
585     public Configuration getConfiguration(String name)
586     {
587         return configuration.subset(SERVICE_PREFIX + name);
588     }
589 
590     /***
591      * Set the application root.
592      *
593      * @param applicationRoot application root
594      */
595     public void setApplicationRoot(String applicationRoot)
596     {
597         this.applicationRoot = applicationRoot;
598     }
599 
600     /***
601      * Get the application root as set by
602      * the parent application.
603      *
604      * @return String application root
605      */
606     public String getApplicationRoot()
607     {
608         return applicationRoot;
609     }
610 }