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 import java.util.LinkedHashMap;
28 import java.util.LinkedHashSet;
29 import java.util.Map;
30 import java.util.Set;
31
32 import org.apache.commons.configuration.Configuration;
33 import org.apache.commons.lang.StringUtils;
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogFactory;
36
37 /**
38 * A generic implementation of a <code>ServiceBroker</code> which
39 * provides:
40 *
41 * <ul>
42 * <li>Maintaining service name to class name mapping, allowing
43 * pluggable service implementations.</li>
44 * <li>Providing <code>Services</code> with a configuration based on
45 * system wide configuration mechanism.</li>
46 * </ul>
47 * <li>Integration of TurbineServiceProviders for looking up
48 * non-local services
49 * </ul>
50 *
51 * @author <a href="mailto:burton@apache.org">Kevin Burton</a>
52 * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a>
53 * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
54 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
55 * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
56 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
57 * @version $Id: BaseServiceBroker.java 1706239 2015-10-01 13:18:35Z tv $
58 */
59 public abstract class BaseServiceBroker implements ServiceBroker
60 {
61 /**
62 * Mapping of Service names to class names, keep order.
63 */
64 private final Map<String, Class<?>> mapping = new LinkedHashMap<String, Class<?>>();
65
66 /**
67 * A repository of Service instances.
68 */
69 private final Hashtable<String, Service> services = new Hashtable<String, Service>();
70
71 /**
72 * Configuration for the services broker.
73 * The configuration should be set by the application
74 * in which the services framework is running.
75 */
76 private Configuration configuration;
77
78 /**
79 * A prefix for <code>Service</code> properties in
80 * TurbineResource.properties.
81 */
82 public static final String SERVICE_PREFIX = "services.";
83
84 /**
85 * A <code>Service</code> property determining its implementing
86 * class name .
87 */
88 public static final String CLASSNAME_SUFFIX = ".classname";
89
90 /**
91 * These are objects that the parent application
92 * can provide so that application specific
93 * services have a mechanism to retrieve specialized
94 * information. For example, in Turbine there are services
95 * that require the RunData object: these services can
96 * retrieve the RunData object that Turbine has placed
97 * in the service manager. This alleviates us of
98 * the requirement of having init(Object) all
99 * together.
100 */
101 private final Hashtable<String, Object> serviceObjects = new Hashtable<String, Object>();
102
103 /** Logging */
104 private static Log log = LogFactory.getLog(BaseServiceBroker.class);
105
106 /**
107 * Application root path as set by the
108 * parent application.
109 */
110 private String applicationRoot;
111
112 /**
113 * mapping from service names to instances of TurbineServiceProviders
114 */
115 private final Hashtable<String, Service> serviceProviderInstanceMap = new Hashtable<String, Service>();
116
117 /**
118 * Default constructor, protected as to only be usable by subclasses.
119 *
120 * This constructor does nothing.
121 */
122 protected BaseServiceBroker()
123 {
124 // nothing to do
125 }
126
127 /**
128 * Set the configuration object for the services broker.
129 * This is the configuration that contains information
130 * about all services in the care of this service
131 * manager.
132 *
133 * @param configuration Broker configuration.
134 */
135 public void setConfiguration(Configuration configuration)
136 {
137 this.configuration = configuration;
138 }
139
140 /**
141 * Get the configuration for this service manager.
142 *
143 * @return Broker configuration.
144 */
145 public Configuration getConfiguration()
146 {
147 return configuration;
148 }
149
150 /**
151 * Initialize this service manager.
152 * @throws InitializationException if the initialization fails
153 */
154 public void init() throws InitializationException
155 {
156 // Check:
157 //
158 // 1. The configuration has been set.
159 // 2. Make sure the application root has been set.
160
161 // FIXME: Make some service framework exceptions to throw in
162 // the event these requirements aren't satisfied.
163
164 // Create the mapping between service names
165 // and their classes.
166 initMapping();
167
168 // Start services that have their 'earlyInit'
169 // property set to 'true'.
170 initServices(false);
171 }
172
173 /**
174 * Set an application specific service object
175 * that can be used by application specific
176 * services.
177 *
178 * @param name name of service object
179 * @param value value of service object
180 */
181 public void setServiceObject(String name, Object value)
182 {
183 serviceObjects.put(name, value);
184 }
185
186 /**
187 * Get an application specific service object.
188 *
189 * @param name the name of the service object
190 * @return Object application specific service object
191 */
192 public Object getServiceObject(String name)
193 {
194 return serviceObjects.get(name);
195 }
196
197 /**
198 * Check recursively if the given checkIfc interface is among the implemented
199 * interfaces
200 *
201 * @param checkIfc interface to check for
202 * @param interfaces interfaces to scan
203 * @return true if the interface is implemented
204 */
205 private boolean checkForInterface(Class<?> checkIfc, Class<?>[] interfaces)
206 {
207 for (Class<?> ifc : interfaces)
208 {
209 if (ifc == checkIfc)
210 {
211 return true;
212 }
213
214 Class<?>[] subInterfaces = ifc.getInterfaces();
215 if (checkForInterface(checkIfc, subInterfaces))
216 {
217 return true;
218 }
219 }
220
221 return false;
222 }
223
224 /**
225 * Creates a mapping between Service names and class names.
226 *
227 * The mapping is built according to settings present in
228 * TurbineResources.properties. The entries should have the
229 * following form:
230 *
231 * <pre>
232 * services.MyService.classname=com.mycompany.MyServiceImpl
233 * services.MyOtherService.classname=com.mycompany.MyOtherServiceImpl
234 * </pre>
235 *
236 * <br>
237 *
238 * Generic ServiceBroker provides no Services.
239 * @throws InitializationException if a service class could not be found
240 */
241 protected void initMapping() throws InitializationException
242 {
243 // we need to temporarily store the earlyInit flags to avoid
244 // ConcurrentModificationExceptions
245 Map<String, String> earlyInitFlags = new LinkedHashMap<String, String>();
246
247 /*
248 * These keys returned in an order that corresponds
249 * to the order the services are listed in
250 * the TR.props.
251 */
252 for (Iterator<String> keys = configuration.getKeys(); keys.hasNext();)
253 {
254 String key = keys.next();
255 String[] keyParts = StringUtils.split(key, ".");
256
257 if ((keyParts.length == 3)
258 && (keyParts[0] + ".").equals(SERVICE_PREFIX)
259 && ("." + keyParts[2]).equals(CLASSNAME_SUFFIX))
260 {
261 String serviceKey = keyParts[1];
262 log.info("Added Mapping for Service: " + serviceKey);
263
264 if (!mapping.containsKey(serviceKey))
265 {
266 String className = configuration.getString(key);
267 try
268 {
269 Class<?> clazz = Class.forName(className);
270 mapping.put(serviceKey, clazz);
271
272 // detect TurbineServiceProviders
273 if (checkForInterface(TurbineServiceProvider.class, clazz.getInterfaces()))
274 {
275 log.info("Found a TurbineServiceProvider: " + serviceKey + " - initializing it early");
276 earlyInitFlags.put(SERVICE_PREFIX + serviceKey + ".earlyInit", "true");
277 }
278 }
279 // those two errors must be passed to the VM
280 catch (ThreadDeath t)
281 {
282 throw t;
283 }
284 catch (OutOfMemoryError t)
285 {
286 throw t;
287 }
288 catch (ClassNotFoundException e)
289 {
290 throw new InitializationException("Class " + className +
291 " is unavailable. Check your jars and classes.", e);
292 }
293 catch (NoClassDefFoundError e)
294 {
295 throw new InitializationException("Class " + className +
296 " is unavailable. Check your jars and classes.", e);
297 }
298 }
299 }
300 }
301
302 for (Map.Entry<String, String> entry : earlyInitFlags.entrySet())
303 {
304 configuration.setProperty(entry.getKey(), entry.getValue());
305 }
306 }
307
308 /**
309 * Determines whether a service is registered in the configured
310 * <code>TurbineResources.properties</code>.
311 *
312 * @param serviceName The name of the service whose existence to check.
313 * @return Registration predicate for the desired services.
314 */
315 @Override
316 public boolean isRegistered(String serviceName)
317 {
318 return (services.get(serviceName) != null);
319 }
320
321 /**
322 * Returns an Iterator over all known service names.
323 *
324 * @return An Iterator of service names.
325 */
326 public Iterator<String> getServiceNames()
327 {
328 return mapping.keySet().iterator();
329 }
330
331 /**
332 * Returns an Iterator over all known service names beginning with
333 * the provided prefix.
334 *
335 * @param prefix The prefix against which to test.
336 * @return An Iterator of service names which match the prefix.
337 */
338 public Iterator<String> getServiceNames(String prefix)
339 {
340 Set<String> keys = new LinkedHashSet<String>(mapping.keySet());
341 for(Iterator<String> key = keys.iterator(); key.hasNext();)
342 {
343 if (!key.next().startsWith(prefix))
344 {
345 key.remove();
346 }
347 }
348
349 return keys.iterator();
350 }
351
352 /**
353 * Performs early initialization of specified service.
354 *
355 * @param name The name of the service (generally the
356 * <code>SERVICE_NAME</code> constant of the service's interface
357 * definition).
358 * @exception InitializationException Initialization of this
359 * service was not successful.
360 */
361 @Override
362 public synchronized void initService(String name)
363 throws InitializationException
364 {
365 // Calling getServiceInstance(name) assures that the Service
366 // implementation has its name and broker reference set before
367 // initialization.
368 Service instance = getServiceInstance(name);
369
370 if (!instance.getInit())
371 {
372 // this call might result in an indirect recursion
373 instance.init();
374 }
375 }
376
377 /**
378 * Performs early initialization of all services. Failed early
379 * initialization of a Service may be non-fatal to the system,
380 * thus any exceptions are logged and the initialization process
381 * continues.
382 */
383 public void initServices()
384 {
385 try
386 {
387 initServices(false);
388 }
389 catch (InstantiationException notThrown)
390 {
391 log.debug("Caught non fatal exception", notThrown);
392 }
393 catch (InitializationException notThrown)
394 {
395 log.debug("Caught non fatal exception", notThrown);
396 }
397 }
398
399 /**
400 * Performs early initialization of all services. You can decide
401 * to handle failed initializations if you wish, but then
402 * after one service fails, the other will not have the chance
403 * to initialize.
404 *
405 * @param report <code>true</code> if you want exceptions thrown.
406 * @throws InstantiationException if the service could not be instantiated
407 * @throws InitializationException if the service could not be initialized
408 */
409 public void initServices(boolean report)
410 throws InstantiationException, InitializationException
411 {
412 if (report)
413 {
414 // Throw exceptions
415 for (Iterator<String> names = getServiceNames(); names.hasNext();)
416 {
417 doInitService(names.next());
418 }
419 }
420 else
421 {
422 // Eat exceptions
423 for (Iterator<String> names = getServiceNames(); names.hasNext();)
424 {
425 try
426 {
427 doInitService(names.next());
428 }
429 // In case of an exception, file an error message; the
430 // system may be still functional, though.
431 catch (InstantiationException e)
432 {
433 log.error(e);
434 }
435 catch (InitializationException e)
436 {
437 log.error(e);
438 }
439 }
440 }
441 log.info("Finished initializing all services!");
442 }
443
444 /**
445 * Internal utility method for use in {@link #initServices(boolean)}
446 * to prevent duplication of code.
447 */
448 private void doInitService(String name)
449 throws InstantiationException, InitializationException
450 {
451 // Only start up services that have their earlyInit flag set.
452 if (getConfiguration(name).getBoolean("earlyInit", false))
453 {
454 log.info("Start Initializing service (early): " + name);
455 initService(name);
456 log.info("Finish Initializing service (early): " + name);
457 }
458 }
459
460 /**
461 * Shuts down a <code>Service</code>, releasing resources
462 * allocated by an <code>Service</code>, and returns it to its
463 * initial (uninitialized) state.
464 *
465 * @param name The name of the <code>Service</code> to be
466 * uninitialized.
467 */
468 @Override
469 public synchronized void shutdownService(String name)
470 {
471 try
472 {
473 Service service = getServiceInstance(name);
474 if (service != null && service.getInit())
475 {
476 service.shutdown();
477
478 if (service.getInit() && service instanceof BaseService)
479 {
480 // BaseService::shutdown() does this by default,
481 // but could've been overriden poorly.
482 ((BaseService) service).setInit(false);
483 }
484 }
485 }
486 catch (InstantiationException e)
487 {
488 // Assuming harmless -- log the error and continue.
489 log.error("Shutdown of a nonexistent Service '"
490 + name + "' was requested", e);
491 }
492 }
493
494 /**
495 * Shuts down all Turbine services, releasing allocated resources and
496 * returning them to their initial (uninitialized) state.
497 */
498 @Override
499 public void shutdownServices()
500 {
501 log.info("Shutting down all services!");
502
503 String serviceName = null;
504
505 /*
506 * Now we want to reverse the order of
507 * this list. This functionality should be added to
508 * the ExtendedProperties in the commons but
509 * this will fix the problem for now.
510 */
511
512 ArrayList<String> reverseServicesList = new ArrayList<String>();
513
514 for (Iterator<String> serviceNames = getServiceNames(); serviceNames.hasNext();)
515 {
516 serviceName = serviceNames.next();
517 reverseServicesList.add(0, serviceName);
518 }
519
520 for (Iterator<String> serviceNames = reverseServicesList.iterator(); serviceNames.hasNext();)
521 {
522 serviceName = serviceNames.next();
523 log.info("Shutting down service: " + serviceName);
524 shutdownService(serviceName);
525 }
526 }
527
528 /**
529 * Returns an instance of requested Service.
530 *
531 * @param name The name of the Service requested.
532 * @return An instance of requested Service.
533 * @exception InstantiationException if the service is unknown or
534 * can't be initialized.
535 */
536 @Override
537 public Object getService(String name) throws InstantiationException
538 {
539 Service service;
540
541 if (this.isLocalService(name))
542 {
543 try
544 {
545 service = getServiceInstance(name);
546 if (!service.getInit())
547 {
548 synchronized (service.getClass())
549 {
550 if (!service.getInit())
551 {
552 log.info("Start Initializing service (late): " + name);
553 service.init();
554 log.info("Finish Initializing service (late): " + name);
555 }
556 }
557 }
558 if (!service.getInit())
559 {
560 // this exception will be caught & rethrown by this very method.
561 // getInit() returning false indicates some initialization issue,
562 // which in turn prevents the InitableBroker from passing a
563 // reference to a working instance of the initable to the client.
564 throw new InitializationException(
565 "init() failed to initialize service " + name);
566 }
567 return service;
568 }
569 catch (InitializationException e)
570 {
571 throw new InstantiationException("Service " + name +
572 " failed to initialize", e);
573 }
574 }
575 else if (this.isNonLocalService(name))
576 {
577 return this.getNonLocalService(name);
578 }
579 else
580 {
581 throw new InstantiationException(
582 "ServiceBroker: unknown service " + name
583 + " requested");
584 }
585 }
586
587 /**
588 * Retrieves an instance of a Service without triggering late
589 * initialization.
590 *
591 * Early initialization of a Service can require access to Service
592 * properties. The Service must have its name and serviceBroker
593 * set by then. Therefore, before calling
594 * Initable.initClass(Object), the class must be instantiated with
595 * InitableBroker.getInitableInstance(), and
596 * Service.setServiceBroker() and Service.setName() must be
597 * called. This calls for two - level accessing the Services
598 * instances.
599 *
600 * @param name The name of the service requested.
601 * @exception InstantiationException The service is unknown or
602 * can't be initialized.
603 */
604 protected Service getServiceInstance(String name)
605 throws InstantiationException
606 {
607 Service service = services.get(name);
608
609 if (service == null)
610 {
611 if (!this.isLocalService(name))
612 {
613 throw new InstantiationException(
614 "ServiceBroker: unknown service " + name
615 + " requested");
616 }
617
618 try
619 {
620 Class<?> clazz = mapping.get(name);
621
622 try
623 {
624 service = (Service) clazz.newInstance();
625
626 // check if the newly created service is also a
627 // service provider - if so then remember it
628 if (service instanceof TurbineServiceProvider)
629 {
630 this.serviceProviderInstanceMap.put(name,service);
631 }
632 }
633 // those two errors must be passed to the VM
634 catch (ClassCastException e)
635 {
636 throw new InstantiationException("Class " + clazz +
637 " doesn't implement the Service interface", e);
638 }
639 catch (ThreadDeath t)
640 {
641 throw t;
642 }
643 catch (OutOfMemoryError t)
644 {
645 throw t;
646 }
647 catch (Throwable t)
648 {
649 throw new InstantiationException("Failed to instantiate " + clazz, t);
650 }
651 }
652 catch (InstantiationException e)
653 {
654 throw new InstantiationException(
655 "Failed to instantiate service " + name, e);
656 }
657 service.setServiceBroker(this);
658 service.setName(name);
659 services.put(name, service);
660 }
661
662 return service;
663 }
664
665 /**
666 * Returns the configuration for the specified service.
667 *
668 * @param name The name of the service.
669 * @return Configuration of requested Service.
670 */
671 @Override
672 public Configuration getConfiguration(String name)
673 {
674 return configuration.subset(SERVICE_PREFIX + name);
675 }
676
677 /**
678 * Set the application root.
679 *
680 * @param applicationRoot application root
681 */
682 public void setApplicationRoot(String applicationRoot)
683 {
684 this.applicationRoot = applicationRoot;
685 }
686
687 /**
688 * Get the application root as set by
689 * the parent application.
690 *
691 * @return String application root
692 */
693 public String getApplicationRoot()
694 {
695 return applicationRoot;
696 }
697
698 /**
699 * Determines if the requested service is managed by this
700 * ServiceBroker.
701 *
702 * @param name The name of the Service requested.
703 * @return true if the service is managed by the this ServiceBroker
704 */
705 protected boolean isLocalService(String name)
706 {
707 return this.mapping.containsKey(name);
708 }
709
710 /**
711 * Determines if the requested service is managed by an initialized
712 * TurbineServiceProvider. We use the service names to lookup
713 * the TurbineServiceProvider to ensure that we get a fully
714 * inititialized service.
715 *
716 * @param name The name of the Service requested.
717 * @return true if the service is managed by a TurbineServiceProvider
718 */
719 protected boolean isNonLocalService(String name)
720 {
721 String serviceName = null;
722 TurbineServiceProvider turbineServiceProvider = null;
723 Enumeration<String> list = this.serviceProviderInstanceMap.keys();
724
725 while (list.hasMoreElements())
726 {
727 serviceName = list.nextElement();
728 turbineServiceProvider = (TurbineServiceProvider) this.getService(serviceName);
729
730 if (turbineServiceProvider.exists(name))
731 {
732 return true;
733 }
734 }
735
736 return false;
737 }
738
739 /**
740 * Get a non-local service managed by a TurbineServiceProvider.
741 *
742 * @param name The name of the Service requested.
743 * @return the requested service
744 * @throws InstantiationException the service couldn't be instantiated
745 */
746 protected Object getNonLocalService(String name)
747 throws InstantiationException
748 {
749 String serviceName = null;
750 TurbineServiceProvider turbineServiceProvider = null;
751 Enumeration<String> list = this.serviceProviderInstanceMap.keys();
752
753 while (list.hasMoreElements())
754 {
755 serviceName = list.nextElement();
756 turbineServiceProvider = (TurbineServiceProvider) this.getService(serviceName);
757
758 if (turbineServiceProvider.exists(name))
759 {
760 return turbineServiceProvider.get(name);
761 }
762 }
763
764 throw new InstantiationException(
765 "ServiceBroker: unknown non-local service " + name
766 + " requested");
767 }
768 }