001: /*
002: * $Id: EngineStarter.java,v 1.46 2007/04/25 14:13:18 agoubard Exp $
003: *
004: * Copyright 2003-2007 Orange Nederland Breedband B.V.
005: * See the COPYRIGHT file for redistribution and use restrictions.
006: */
007: package org.xins.server;
008:
009: import java.lang.reflect.Field;
010:
011: import javax.servlet.ServletConfig;
012: import javax.servlet.ServletContext;
013: import javax.servlet.ServletException;
014:
015: import org.xins.common.MandatoryArgumentChecker;
016: import org.xins.common.Utils;
017: import org.xins.common.collections.InvalidPropertyValueException;
018: import org.xins.common.collections.MissingRequiredPropertyException;
019: import org.xins.common.collections.PropertyReader;
020: import org.xins.common.servlet.ServletConfigPropertyReader;
021: import org.xins.common.text.TextUtils;
022:
023: import org.xins.logdoc.AbstractLog;
024: import org.xins.logdoc.ExceptionUtils;
025: import org.xins.logdoc.LogCentral;
026: import org.xins.logdoc.UnsupportedLocaleException;
027:
028: /**
029: * XINS engine starter.
030: *
031: * @version $Revision: 1.46 $ $Date: 2007/04/25 14:13:18 $
032: * @author <a href="mailto:mees.witteman@orange-ftgroup.com">Mees Witteman</a>
033: * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
034: */
035: final class EngineStarter {
036:
037: /**
038: * The name of the bootstrap property that specifies the name of the
039: * API class to load.
040: */
041: private static final String API_CLASS_PROPERTY = "org.xins.api.class";
042:
043: /**
044: * The name of the bootstrap property that specifies the name of the
045: * API.
046: */
047: private static final String API_NAME_PROPERTY = "org.xins.api.name";
048:
049: /**
050: * The name of the bootstrap property that specifies the version with which the
051: * API was built.
052: */
053: private static final String API_BUILD_VERSION_PROPERTY = "org.xins.api.build.version";
054:
055: /**
056: * The servlet config. Never <code>null</code>.
057: */
058: private ServletConfig _config;
059:
060: /**
061: * Constructor for the <code>EngineStarter</code> class.
062: *
063: * @param config
064: * servlet configuration, cannot be <code>null</code> and is guaranteed
065: * to have a {@link ServletContext} associated with it.
066: *
067: * @throws IllegalArgumentException
068: * if <code>config == null</code>.
069: */
070: EngineStarter(ServletConfig config) throws IllegalArgumentException {
071:
072: // Check preconditions
073: MandatoryArgumentChecker.check("config", config);
074:
075: // Store data
076: _config = config;
077: }
078:
079: /**
080: * Constructs a new <code>ServletException</code> with the specified cause.
081: *
082: * @param t
083: * the cause for the {@link ServletException}, can be <code>null</code>.
084: *
085: * @return
086: * the new {@link ServletException}, that has <code>t</code> registered
087: * as the cause for it, never <code>null</code>.
088: *
089: * @see ExceptionUtils#setCause(Throwable,Throwable)
090: */
091: static ServletException servletExceptionFor(Throwable t) {
092:
093: ServletException servletException;
094:
095: // If the cause is already a ServletException, use it unchanged
096: if (t instanceof ServletException) {
097: servletException = (ServletException) t;
098:
099: // If a cause has been specified, then use that
100: } else if (t != null) {
101: servletException = new ServletException();
102: ExceptionUtils.setCause(servletException, t);
103:
104: // Otherwise just create a vanilla ServletException
105: } else {
106: servletException = new ServletException();
107: }
108:
109: return servletException;
110: }
111:
112: /**
113: * Logs server version, warns if server version differs from common version
114: * and warns if the server version is not a production release.
115: */
116: void logBootMessages() {
117:
118: // Determine the ServletContext
119: ServletContext context = _config.getServletContext();
120:
121: // Determine servlet container info
122: String containerInfo = context.getServerInfo();
123: if (containerInfo == null) {
124: throw Utils
125: .logProgrammingError("ServletContext.getServerInfo() returned null.");
126: }
127:
128: // Determine Java VM info
129: String jvmVendor = System.getProperty("java.vm.vendor");
130: String jvmName = System.getProperty("java.vm.name");
131: String jvmVersion = System.getProperty("java.vm.version");
132: String jvmInfo = jvmVendor + " " + jvmName + " " + jvmVersion;
133:
134: // Determine operating system info
135: String osName = System.getProperty("os.name");
136: String osVersion = System.getProperty("os.version");
137: String osArch = System.getProperty("os.arch");
138: String osInfo = osName + " " + osVersion + "/" + osArch;
139:
140: // Log: Bootstrapping XINS/Java Server Framework
141: String serverVersion = Library.getVersion();
142: Log.log_3200(serverVersion, containerInfo, jvmInfo, osInfo);
143:
144: // Warn if Server version differs from Common version
145: String commonVersion = org.xins.common.Library.getVersion();
146: if (!serverVersion.equals(commonVersion)) {
147: Log.log_3226(serverVersion, commonVersion);
148: }
149:
150: // Warn if the current XINS version is not a production version
151: if (!Library.isProductionRelease(serverVersion)) {
152: Log.log_3227(serverVersion);
153: }
154:
155: // Warn if API build version is more recent than running version
156: if (Library.isProductionRelease(serverVersion)) {
157: String propName = API_BUILD_VERSION_PROPERTY;
158: String buildVersion = _config.getInitParameter(propName);
159: if (buildVersion == null) {
160: Log.log_3232(propName);
161: }
162: }
163: }
164:
165: /**
166: * Constructs the API.
167: *
168: * @return The constructed API.
169: *
170: * @throws ServletException
171: * if the API can not be constructed from the values in the config object
172: */
173: API constructAPI() throws ServletException {
174:
175: String apiClassName = determineAPIClassName();
176: Class apiClass = loadAPIClass(apiClassName);
177: API api = getAPIFromSingletonField(apiClassName, apiClass);
178:
179: checkAPIConstruction(apiClassName, apiClass, api);
180:
181: return api;
182: }
183:
184: /**
185: * Checks the construction of the API.
186: *
187: * @param apiClassName
188: * the name of the API class, cannot be <code>null</code>.
189: *
190: * @param apiClass
191: * The API class, cannot be <code>null</code>.
192: *
193: * @param api
194: * The API instance self, cannot be <code>null</code>.
195: *
196: * @throws ServletException
197: * if the API is <code>null</code> or if the API class is not equal to
198: * <code>apiClass</code>.
199: */
200: private void checkAPIConstruction(String apiClassName,
201: Class apiClass, API api) throws ServletException {
202:
203: // Make sure that the value of the field is not null
204: if (api == null) {
205: String detail = "Value of static field SINGLETON in class "
206: + apiClassName + " is null.";
207: Log.log_3208(API_CLASS_PROPERTY, apiClassName, detail);
208: throw new ServletException();
209: }
210:
211: // Make sure that the value of the field is an instance of that class
212: if (api.getClass() != apiClass) {
213: String detail = "Value of static field SINGLETON in class "
214: + apiClassName
215: + " is not an instance of that class.";
216: Log.log_3208(API_CLASS_PROPERTY, apiClassName, detail);
217: throw new ServletException();
218: }
219: }
220:
221: /**
222: * Gets the API from the singleton field that is available on all API's.
223: *
224: * @param apiClassName
225: * the api class name, cannot be <code>null</code>.
226: *
227: * @param apiClass
228: * the api class, cannot be <code>null</code>.
229: *
230: * @return
231: * an instance of the api object, can be <code>null</code>.
232: *
233: * @throws ServletException
234: * if the apiClass doesn't have a singleton field or
235: * if the value of the field can not be cast to the API class.
236: */
237: private API getAPIFromSingletonField(String apiClassName,
238: Class apiClass) throws ServletException {
239:
240: // Get the SINGLETON field and the value of it
241: Field singletonField;
242: API api;
243: try {
244: singletonField = apiClass.getDeclaredField("SINGLETON");
245: api = (API) singletonField.get(null);
246: } catch (Throwable exception) {
247: String detail = "Caught unexpected "
248: + exception.getClass().getName()
249: + " while retrieving the value of the static field SINGLETON in class "
250: + apiClassName + '.';
251: Utils.logProgrammingError(detail, exception);
252: Log.log_3208(API_CLASS_PROPERTY, apiClassName, detail);
253: throw servletExceptionFor(exception);
254: }
255: return api;
256: }
257:
258: /**
259: * Loads the class with the given name and performs checks on the loaded
260: * class.
261: *
262: * @param apiClassName
263: * the name of the API class that should be loaded, cannot be
264: * <code>null</code>.
265: *
266: * @return
267: * the loaded API class, never <code>null</code>.
268: *
269: * @throws IllegalArgumentException
270: * if <code>apiClassName == null</code>.
271: *
272: * @throws ServletException
273: * if the API class loading failed.
274: */
275: private Class loadAPIClass(String apiClassName)
276: throws IllegalArgumentException, ServletException {
277:
278: // Check preconditions
279: MandatoryArgumentChecker.check("apiClassName", apiClassName);
280:
281: // Load the API class
282: Class apiClass;
283: try {
284: apiClass = Class.forName(apiClassName);
285: } catch (Throwable exception) {
286: Log.log_3207(exception, API_CLASS_PROPERTY, apiClassName);
287: throw servletExceptionFor(exception);
288: }
289:
290: // Check that the loaded API class is derived from the API base class
291: if (!API.class.isAssignableFrom(apiClass)) {
292: String detail = "Class " + apiClassName
293: + " is not derived from " + API.class.getName()
294: + '.';
295: Log.log_3208(API_CLASS_PROPERTY, apiClassName, detail);
296: throw new ServletException();
297: }
298: return apiClass;
299: }
300:
301: /**
302: * Determines the API class name from the config file.
303: *
304: * @return The API class name
305: *
306: * @throws ServletException if the class name could not be determined from
307: * the init parameters.
308: */
309: private String determineAPIClassName() throws ServletException {
310: String apiClassName = _config
311: .getInitParameter(API_CLASS_PROPERTY);
312: apiClassName = TextUtils.isEmpty(apiClassName) ? null
313: : apiClassName.trim();
314: if (apiClassName == null) {
315: Log.log_3206(API_CLASS_PROPERTY);
316: throw new ServletException();
317: }
318: return apiClassName;
319: }
320:
321: /**
322: * Calls the bootstrap on the API and logs exceptions in case of an error.
323: *
324: * @param api
325: * the API to bootstrap, never <code>null</code>.
326: *
327: * @return
328: * a {@link PropertyReader} for the bootstrap properties, never
329: * <code>null</code>.
330: *
331: * @throws ServletException
332: * if the bootstrap of the api fails.
333: */
334: PropertyReader bootstrap(API api) throws ServletException {
335:
336: // Convert ServletConfig to PropertyReader
337: PropertyReader properties = new ServletConfigPropertyReader(
338: _config);
339:
340: // Determine at what level should the stack traces be displayed
341: String stackTraceAtMessageLevel = properties
342: .get(LogCentral.LOG_STACK_TRACE_AT_MESSAGE_LEVEL);
343: if ("true".equals(stackTraceAtMessageLevel)) {
344: LogCentral.setStackTraceAtMessageLevel(true);
345: } else if ("false".equals(stackTraceAtMessageLevel)) {
346: LogCentral.setStackTraceAtMessageLevel(false);
347: } else if (stackTraceAtMessageLevel != null) {
348: throw new ServletException("Incorrect value for the "
349: + LogCentral.LOG_STACK_TRACE_AT_MESSAGE_LEVEL
350: + " bootstrap property.");
351: }
352:
353: // Bootstrap the API self
354: Throwable caught;
355: try {
356: api.bootstrap(properties);
357: caught = null;
358:
359: // Missing required property
360: } catch (MissingRequiredPropertyException exception) {
361: Log.log_3209(exception.getPropertyName(), exception
362: .getDetail());
363: caught = exception;
364:
365: // Invalid property value
366: } catch (InvalidPropertyValueException exception) {
367: Log.log_3210(exception.getPropertyName(), exception
368: .getPropertyValue(), exception.getReason());
369: caught = exception;
370:
371: // Other bootstrap error
372: } catch (Throwable exception) {
373: Log.log_3211(exception);
374: caught = exception;
375: }
376:
377: // Throw a ServletException if the bootstrap failed
378: if (caught != null) {
379: ServletException se = new ServletException(
380: "API bootstrap failed.");
381: ExceptionUtils.setCause(se, caught);
382: throw se;
383: }
384:
385: return properties;
386: }
387:
388: /**
389: * Attempts to load the logdoc class and performs checks on the class.
390: *
391: * @throws ServletException
392: * If the log doc class can not be loaded.
393: */
394: void loadLogdoc() throws ServletException {
395:
396: String logdocClassName = determineLogdocName();
397:
398: try {
399: // Attempt to load the Logdoc 'Log' class. This should execute the
400: // static initializer, which is what we want.
401: Class logdocClass = Class.forName(logdocClassName);
402:
403: // Is the loaded class really a Logdoc 'Log' class or just some
404: // other class that is coincedentally called 'Log' ?
405: // If it is, then the API indeed uses Logdoc logging
406: if (AbstractLog.class.isAssignableFrom(logdocClass)) {
407: Log.log_3233();
408:
409: // The API does not use Logdoc logging
410: } else {
411: Log.log_3234();
412: }
413:
414: // There is no 'Log' class in the API package
415: } catch (ClassNotFoundException cnfe) {
416: Log.log_3234();
417:
418: // The locale is not supported
419: } catch (UnsupportedLocaleException exception) {
420: Log.log_3309(exception.getLocale());
421: throw servletExceptionFor(exception);
422:
423: // Other unexpected exception
424: } catch (Throwable exception) {
425: Utils
426: .logProgrammingError(
427: "Unexpected exception while loading Logdoc Log class for API.",
428: exception);
429: }
430: }
431:
432: /**
433: * Determines the name of the Logdoc Log class for the API. If there is a
434: * Logdoc Log class for the API, then it should match the returned
435: * fully-qualified class name.
436: *
437: * @return
438: * the fully-qualified name of the Logdoc Log class, never
439: * <code>null</code>.
440: */
441: private String determineLogdocName() {
442:
443: // Determine the name of the API class
444: String apiClassName = _config
445: .getInitParameter(API_CLASS_PROPERTY);
446:
447: // Determine the class prefix, which is everything before the
448: // unqualified class name
449: String classPrefix;
450: int lastDot = apiClassName.lastIndexOf('.');
451: if (lastDot < 0) {
452: classPrefix = "";
453: } else {
454: classPrefix = apiClassName.substring(0, lastDot + 1);
455: }
456:
457: // The name of the Logdoc Log class is always "Log"
458: String logdocClassName = classPrefix + "Log";
459:
460: return logdocClassName;
461: }
462:
463: /**
464: * Determines the API name.
465: *
466: * @return
467: * the API name, or <code>"-"</code> if unknown, never
468: * <code>null</code>.
469: *
470: * @throws ServletException
471: * if the API name is not set.
472: */
473: String determineAPIName() throws ServletException {
474:
475: // Determine the name of the API
476: String apiName = _config.getInitParameter(API_NAME_PROPERTY);
477: if (apiName != null) {
478: apiName = apiName.trim();
479: }
480:
481: // If the name is not set, then return a hyphen instead
482: if (TextUtils.isEmpty(apiName)) {
483: Log.log_3232(API_NAME_PROPERTY);
484: throw new ServletException("The API name is not set.");
485: } else {
486: apiName = apiName.trim();
487: Log.log_3235(apiName);
488: }
489:
490: return apiName;
491: }
492:
493: /**
494: * Registers the API MBean.
495: *
496: * @param api
497: * the API, never <code>null</code>.
498: */
499: void registerMBean(API api) {
500: try {
501: APIManager.registerMBean(api);
502:
503: // If for any reason it doesn't work, ignore.
504: // For example if the server is running on Java 1.4 a ClassNotFoundException may be thrown.
505: } catch (Throwable ex) {
506: Log.log_3249(ex.getMessage());
507: }
508: }
509: }
|