View Javadoc

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