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: import java.util.ArrayList;
023: import java.util.Hashtable;
024: import java.util.Iterator;
025:
026: import org.apache.commons.configuration.BaseConfiguration;
027: import org.apache.commons.configuration.Configuration;
028: import org.apache.commons.lang.StringUtils;
029: import org.apache.commons.logging.Log;
030: import org.apache.commons.logging.LogFactory;
031:
032: /**
033: * A generic implementation of a <code>ServiceBroker</code> which
034: * provides:
035: *
036: * <ul>
037: * <li>Maintaining service name to class name mapping, allowing
038: * plugable service implementations.</li>
039: * <li>Providing <code>Services</code> with a configuration based on
040: * system wide configuration mechanism.</li>
041: * </ul>
042: *
043: * @author <a href="mailto:burton@apache.org">Kevin Burton</a>
044: * @author <a href="mailto:krzewski@e-point.pl">Rafal Krzewski</a>
045: * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
046: * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
047: * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
048: * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
049: * @version $Id: BaseServiceBroker.java 534527 2007-05-02 16:10:59Z tv $
050: */
051: public abstract class BaseServiceBroker implements ServiceBroker {
052: /**
053: * Mapping of Service names to class names.
054: */
055: protected Configuration mapping = new BaseConfiguration();
056:
057: /**
058: * A repository of Service instances.
059: */
060: protected Hashtable services = new Hashtable();
061:
062: /**
063: * Configuration for the services broker.
064: * The configuration should be set by the application
065: * in which the services framework is running.
066: */
067: protected Configuration configuration;
068:
069: /**
070: * A prefix for <code>Service</code> properties in
071: * TurbineResource.properties.
072: */
073: public static final String SERVICE_PREFIX = "services.";
074:
075: /**
076: * A <code>Service</code> property determining its implementing
077: * class name .
078: */
079: public static final String CLASSNAME_SUFFIX = ".classname";
080:
081: /**
082: * These are objects that the parent application
083: * can provide so that application specific
084: * services have a mechanism to retrieve specialized
085: * information. For example, in Turbine there are services
086: * that require the RunData object: these services can
087: * retrieve the RunData object that Turbine has placed
088: * in the service manager. This alleviates us of
089: * the requirement of having init(Object) all
090: * together.
091: */
092: protected Hashtable serviceObjects = new Hashtable();
093:
094: /** Logging */
095: private static Log log = LogFactory.getLog(BaseServiceBroker.class);
096:
097: /**
098: * Application root path as set by the
099: * parent application.
100: */
101: protected String applicationRoot;
102:
103: /**
104: * Default constructor, protected as to only be useable by subclasses.
105: *
106: * This constructor does nothing.
107: */
108: protected BaseServiceBroker() {
109: }
110:
111: /**
112: * Set the configuration object for the services broker.
113: * This is the configuration that contains information
114: * about all services in the care of this service
115: * manager.
116: *
117: * @param configuration Broker configuration.
118: */
119: public void setConfiguration(Configuration configuration) {
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: return configuration;
130: }
131:
132: /**
133: * Initialize this service manager.
134: */
135: public void init() throws InitializationException {
136: // Check:
137: //
138: // 1. The configuration has been set.
139: // 2. Make sure the application root has been set.
140:
141: // FIXME: Make some service framework exceptions to throw in
142: // the event these requirements aren't satisfied.
143:
144: // Create the mapping between service names
145: // and their classes.
146: initMapping();
147:
148: // Start services that have their 'earlyInit'
149: // property set to 'true'.
150: initServices(false);
151: }
152:
153: /**
154: * Set an application specific service object
155: * that can be used by application specific
156: * services.
157: *
158: * @param name name of service object
159: * @param value value of service object
160: */
161: public void setServiceObject(String name, Object value) {
162: serviceObjects.put(name, value);
163: }
164:
165: /**
166: * Get an application specific service object.
167: *
168: * @return Object application specific service object
169: */
170: public Object getServiceObject(String name) {
171: return serviceObjects.get(name);
172: }
173:
174: /**
175: * Creates a mapping between Service names and class names.
176: *
177: * The mapping is built according to settings present in
178: * TurbineResources.properties. The entries should have the
179: * following form:
180: *
181: * <pre>
182: * services.MyService.classname=com.mycompany.MyServiceImpl
183: * services.MyOtherService.classname=com.mycompany.MyOtherServiceImpl
184: * </pre>
185: *
186: * <br>
187: *
188: * Generic ServiceBroker provides no Services.
189: */
190: protected void initMapping() {
191: /*
192: * These keys returned in an order that corresponds
193: * to the order the services are listed in
194: * the TR.props.
195: *
196: * When the mapping is created we use a Configuration
197: * object to ensure that the we retain the order
198: * in which the order the keys are returned.
199: *
200: * There's no point in retrieving an ordered set
201: * of keys if they aren't kept in order :-)
202: */
203: for (Iterator keys = configuration.getKeys(); keys.hasNext();) {
204: String key = (String) keys.next();
205: String[] keyParts = StringUtils.split(key, ".");
206:
207: if ((keyParts.length == 3)
208: && (keyParts[0] + ".").equals(SERVICE_PREFIX)
209: && ("." + keyParts[2]).equals(CLASSNAME_SUFFIX)) {
210: String serviceKey = keyParts[1];
211: log.info("Added Mapping for Service: " + serviceKey);
212:
213: if (!mapping.containsKey(serviceKey)) {
214: mapping.setProperty(serviceKey, configuration
215: .getString(key));
216: }
217: }
218: }
219: }
220:
221: /**
222: * Determines whether a service is registered in the configured
223: * <code>TurbineResources.properties</code>.
224: *
225: * @param serviceName The name of the service whose existance to check.
226: * @return Registration predicate for the desired services.
227: */
228: public boolean isRegistered(String serviceName) {
229: return (services.get(serviceName) != null);
230: }
231:
232: /**
233: * Returns an Iterator over all known service names.
234: *
235: * @return An Iterator of service names.
236: */
237: public Iterator getServiceNames() {
238: return mapping.getKeys();
239: }
240:
241: /**
242: * Returns an Iterator over all known service names beginning with
243: * the provided prefix.
244: *
245: * @param prefix The prefix against which to test.
246: * @return An Iterator of service names which match the prefix.
247: */
248: public Iterator getServiceNames(String prefix) {
249: return mapping.getKeys(prefix);
250: }
251:
252: /**
253: * Performs early initialization of specified service.
254: *
255: * @param name The name of the service (generally the
256: * <code>SERVICE_NAME</code> constant of the service's interface
257: * definition).
258: * @exception InitializationException Initialization of this
259: * service was not successful.
260: */
261: public synchronized void initService(String name)
262: throws InitializationException {
263: // Calling getServiceInstance(name) assures that the Service
264: // implementation has its name and broker reference set before
265: // initialization.
266: Service instance = getServiceInstance(name);
267:
268: if (!instance.getInit()) {
269: // this call might result in an indirect recursion
270: instance.init();
271: }
272: }
273:
274: /**
275: * Performs early initialization of all services. Failed early
276: * initialization of a Service may be non-fatal to the system,
277: * thus any exceptions are logged and the initialization process
278: * continues.
279: */
280: public void initServices() {
281: try {
282: initServices(false);
283: } catch (InstantiationException notThrown) {
284: log.debug("Caught non fatal exception", notThrown);
285: } catch (InitializationException notThrown) {
286: log.debug("Caught non fatal exception", notThrown);
287: }
288: }
289:
290: /**
291: * Performs early initialization of all services. You can decide
292: * to handle failed initializations if you wish, but then
293: * after one service fails, the other will not have the chance
294: * to initialize.
295: *
296: * @param report <code>true</code> if you want exceptions thrown.
297: */
298: public void initServices(boolean report)
299: throws InstantiationException, InitializationException {
300: if (report) {
301: // Throw exceptions
302: for (Iterator names = getServiceNames(); names.hasNext();) {
303: doInitService((String) names.next());
304: }
305: } else {
306: // Eat exceptions
307: for (Iterator names = getServiceNames(); names.hasNext();) {
308: try {
309: doInitService((String) names.next());
310: }
311: // In case of an exception, file an error message; the
312: // system may be still functional, though.
313: catch (InstantiationException e) {
314: log.error(e);
315: } catch (InitializationException e) {
316: log.error(e);
317: }
318: }
319: }
320: log.info("Finished initializing all services!");
321: }
322:
323: /**
324: * Internal utility method for use in {@link #initServices(boolean)}
325: * to prevent duplication of code.
326: */
327: private void doInitService(String name)
328: throws InstantiationException, InitializationException {
329: // Only start up services that have their earlyInit flag set.
330: if (getConfiguration(name).getBoolean("earlyInit", false)) {
331: log.info("Start Initializing service (early): " + name);
332: initService(name);
333: log.info("Finish Initializing service (early): " + name);
334: }
335: }
336:
337: /**
338: * Shuts down a <code>Service</code>, releasing resources
339: * allocated by an <code>Service</code>, and returns it to its
340: * initial (uninitialized) state.
341: *
342: * @param name The name of the <code>Service</code> to be
343: * uninitialized.
344: */
345: public synchronized void shutdownService(String name) {
346: try {
347: Service service = getServiceInstance(name);
348: if (service != null && service.getInit()) {
349: service.shutdown();
350: if (service.getInit() && service instanceof BaseService) {
351: // BaseService::shutdown() does this by default,
352: // but could've been overriden poorly.
353: ((BaseService) service).setInit(false);
354: }
355: }
356: } catch (InstantiationException e) {
357: // Assuming harmless -- log the error and continue.
358: log.error("Shutdown of a nonexistent Service '" + name
359: + "' was requested", e);
360: }
361: }
362:
363: /**
364: * Shuts down all Turbine services, releasing allocated resources and
365: * returning them to their initial (uninitialized) state.
366: */
367: public void shutdownServices() {
368: log.info("Shutting down all services!");
369:
370: String serviceName = null;
371:
372: /*
373: * Now we want to reverse the order of
374: * this list. This functionality should be added to
375: * the ExtendedProperties in the commons but
376: * this will fix the problem for now.
377: */
378:
379: ArrayList reverseServicesList = new ArrayList();
380:
381: for (Iterator serviceNames = getServiceNames(); serviceNames
382: .hasNext();) {
383: serviceName = (String) serviceNames.next();
384: reverseServicesList.add(0, serviceName);
385: }
386:
387: for (Iterator serviceNames = reverseServicesList.iterator(); serviceNames
388: .hasNext();) {
389: serviceName = (String) serviceNames.next();
390: log.info("Shutting down service: " + serviceName);
391: shutdownService(serviceName);
392: }
393: }
394:
395: /**
396: * Returns an instance of requested Service.
397: *
398: * @param name The name of the Service requested.
399: * @return An instance of requested Service.
400: * @exception InstantiationException if the service is unknown or
401: * can't be initialized.
402: */
403: public Service getService(String name)
404: throws InstantiationException {
405: Service service;
406: try {
407: service = getServiceInstance(name);
408: if (!service.getInit()) {
409: synchronized (service.getClass()) {
410: if (!service.getInit()) {
411: log.info("Start Initializing service (late): "
412: + name);
413: service.init();
414: log.info("Finish Initializing service (late): "
415: + name);
416: }
417: }
418: }
419: if (!service.getInit()) {
420: // this exception will be caught & rethrown by this very method.
421: // getInit() returning false indicates some initialization issue,
422: // which in turn prevents the InitableBroker from passing a
423: // reference to a working instance of the initable to the client.
424: throw new InitializationException(
425: "init() failed to initialize service " + name);
426: }
427: return service;
428: } catch (InitializationException e) {
429: throw new InstantiationException("Service " + name
430: + " failed to initialize", e);
431: }
432: }
433:
434: /**
435: * Retrieves an instance of a Service without triggering late
436: * initialization.
437: *
438: * Early initialization of a Service can require access to Service
439: * properties. The Service must have its name and serviceBroker
440: * set by then. Therefore, before calling
441: * Initable.initClass(Object), the class must be instantiated with
442: * InitableBroker.getInitableInstance(), and
443: * Service.setServiceBroker() and Service.setName() must be
444: * called. This calls for two - level accessing the Services
445: * instances.
446: *
447: * @param name The name of the service requested.
448: * @exception InstantiationException The service is unknown or
449: * can't be initialized.
450: */
451: protected Service getServiceInstance(String name)
452: throws InstantiationException {
453: Service service = (Service) services.get(name);
454:
455: if (service == null) {
456: String className = mapping.getString(name);
457: if (StringUtils.isEmpty(className)) {
458: throw new InstantiationException(
459: "ServiceBroker: unknown service " + name
460: + " requested");
461: }
462: try {
463: service = (Service) services.get(className);
464:
465: if (service == null) {
466: try {
467: service = (Service) Class.forName(className)
468: .newInstance();
469: }
470: // those two errors must be passed to the VM
471: catch (ThreadDeath t) {
472: throw t;
473: } catch (OutOfMemoryError t) {
474: throw t;
475: } catch (Throwable t) {
476: // Used to indicate error condition.
477: String msg = null;
478:
479: if (t instanceof NoClassDefFoundError) {
480: msg = "A class referenced by "
481: + className
482: + " is unavailable. Check your jars and classes.";
483: } else if (t instanceof ClassNotFoundException) {
484: msg = "Class "
485: + className
486: + " is unavailable. Check your jars and classes.";
487: } else if (t instanceof ClassCastException) {
488: msg = "Class "
489: + className
490: + " doesn't implement the Service interface";
491: } else {
492: msg = "Failed to instantiate " + className;
493: }
494:
495: throw new InstantiationException(msg, t);
496: }
497: }
498: } catch (ClassCastException e) {
499: throw new InstantiationException(
500: "ServiceBroker: Class "
501: + className
502: + " does not implement Service interface.",
503: e);
504: } catch (InstantiationException e) {
505: throw new InstantiationException(
506: "Failed to instantiate service " + name, e);
507: }
508: service.setServiceBroker(this );
509: service.setName(name);
510: services.put(name, service);
511: }
512:
513: return service;
514: }
515:
516: /**
517: * Returns the configuration for the specified service.
518: *
519: * @param name The name of the service.
520: * @return Configuration of requested Service.
521: */
522: public Configuration getConfiguration(String name) {
523: return configuration.subset(SERVICE_PREFIX + name);
524: }
525:
526: /**
527: * Set the application root.
528: *
529: * @param applicationRoot application root
530: */
531: public void setApplicationRoot(String applicationRoot) {
532: this .applicationRoot = applicationRoot;
533: }
534:
535: /**
536: * Get the application root as set by
537: * the parent application.
538: *
539: * @return String application root
540: */
541: public String getApplicationRoot() {
542: return applicationRoot;
543: }
544: }
|