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
23 import java.util.ArrayList;
24 import java.util.Enumeration;
25 import java.util.Hashtable;
26 import java.util.Iterator;
27
28 import org.apache.commons.configuration.BaseConfiguration;
29 import org.apache.commons.configuration.Configuration;
30 import org.apache.commons.lang.StringUtils;
31 import org.apache.commons.logging.Log;
32 import org.apache.commons.logging.LogFactory;
33
34 /**
35 * A generic implementation of a <code>ServiceBroker</code> which
36 * provides:
37 *
38 * <ul>
39 * <li>Maintaining service name to class name mapping, allowing
40 * plugable service implementations.</li>
41 * <li>Providing <code>Services</code> with a configuration based on
42 * system wide configuration mechanism.</li>
43 * </ul>
44 * <li>Integration of TurbineServiceProviders for looking up
45 * non-local services
46 * </ul>
47 *
48 * @author <a href="mailto:burton@apache.org">Kevin Burton</a>
49 * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a>
50 * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
51 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
52 * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
53 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
54 * @version $Id: BaseServiceBroker.java 1066945 2011-02-03 20:27:59Z ludwig $
55 */
56 public abstract class BaseServiceBroker implements ServiceBroker
57 {
58 /**
59 * Mapping of Service names to class names.
60 */
61 private Configuration mapping = new BaseConfiguration();
62
63 /**
64 * A repository of Service instances.
65 */
66 private Hashtable<String, Service> services = new Hashtable<String, Service>();
67
68 /**
69 * Configuration for the services broker.
70 * The configuration should be set by the application
71 * in which the services framework is running.
72 */
73 private Configuration configuration;
74
75 /**
76 * A prefix for <code>Service</code> properties in
77 * TurbineResource.properties.
78 */
79 public static final String SERVICE_PREFIX = "services.";
80
81 /**
82 * A <code>Service</code> property determining its implementing
83 * class name .
84 */
85 public static final String CLASSNAME_SUFFIX = ".classname";
86
87 /**
88 * These are objects that the parent application
89 * can provide so that application specific
90 * services have a mechanism to retrieve specialized
91 * information. For example, in Turbine there are services
92 * that require the RunData object: these services can
93 * retrieve the RunData object that Turbine has placed
94 * in the service manager. This alleviates us of
95 * the requirement of having init(Object) all
96 * together.
97 */
98 private Hashtable<String, Object> serviceObjects = new Hashtable<String, Object>();
99
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 }