Java tutorial
/** * Copyright 2011 meltmedia * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.xchain.framework.lifecycle; import static org.xchain.framework.lifecycle.XmlFactoryLifecycle.DEFAULT_SAX_PARSER_FACTORY_NAME; import static org.xchain.framework.lifecycle.XmlFactoryLifecycle.DEFAULT_TRANSFORMER_FACTORY_NAME; import static org.xchain.framework.lifecycle.XmlFactoryLifecycle.DEFAULT_DOCUMENT_BUILDER_FACTORY_NAME; import static org.xchain.framework.lifecycle.XmlFactoryLifecycle.SAXON_FACTORY_CLASS_NAME; import static org.xchain.framework.lifecycle.XmlFactoryLifecycle.SAXON_FACTORY_NAME; import static org.xchain.framework.lifecycle.XmlFactoryLifecycle.XALAN_FACTORY_CLASS_NAME; import static org.xchain.framework.lifecycle.XmlFactoryLifecycle.XALAN_FACTORY_NAME; import static org.xchain.framework.lifecycle.XmlFactoryLifecycle.XSLTC_FACTORY_CLASS_NAME; import static org.xchain.framework.lifecycle.XmlFactoryLifecycle.XSLTC_FACTORY_NAME; import static org.xchain.framework.lifecycle.XmlFactoryLifecycle.JOOST_FACTORY_CLASS_NAME; import static org.xchain.framework.lifecycle.XmlFactoryLifecycle.JOOST_FACTORY_NAME; import static org.xchain.framework.lifecycle.XmlFactoryLifecycle.getSaxParserFactoryFactory; import static org.xchain.framework.lifecycle.XmlFactoryLifecycle.getTransformerFactoryFactory; import static org.xchain.framework.lifecycle.XmlFactoryLifecycle.getDocumentBuilderFactory; import static org.xchain.framework.lifecycle.XmlFactoryLifecycle.putSaxParserFactoryFactory; import static org.xchain.framework.lifecycle.XmlFactoryLifecycle.putTransformerFactoryFactory; import static org.xchain.framework.lifecycle.XmlFactoryLifecycle.putDocumentBuilderFactoryFactory; import static org.xchain.framework.lifecycle.XmlFactoryLifecycle.removeSaxParserFactoryFactory; import static org.xchain.framework.lifecycle.XmlFactoryLifecycle.removeTransformerFactoryFactory; import static org.xchain.framework.lifecycle.XmlFactoryLifecycle.removeDocumentBuilderFactoryFactory; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.xchain.framework.scanner.ScanException; import org.xchain.framework.scanner.ScannerLifecycle; import org.apache.commons.beanutils.ConvertUtils; import org.apache.commons.beanutils.Converter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xchain.framework.jxpath.Scope; import org.xchain.framework.net.UrlSourceUtil; import org.xchain.framework.net.protocol.resource.ContextClassLoaderUrlTranslationStrategy; import org.xchain.framework.net.protocol.resource.ResourceUrlConnection; import org.xchain.framework.net.strategy.BaseUrlUrlTranslationStrategy; import org.xchain.framework.net.strategy.CompositeUrlTranslationStrategy; import org.xchain.framework.util.QNameConverter; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * The main class of the xchain framework. When the lifecycle is started the LifecycleStepScanner will scan for LifecycleSteps * in the current context class loader. All LifecycleSteps will then be started with the Lifecycle. When the Lifecycle stops * all the LifecycleSteps started when the Lifecycle started will be stopped in a LIFO manner. * * @author Christian Trimble * @author Devon Tackett * @author John Trimble * @author Josh Kennedy * * @see LifecycleStep * @see LifecycleStepScanner */ @LifecycleClass(uri = "http://www.xchain.org/framework/lifecycle") public final class Lifecycle { public static Logger log = LoggerFactory.getLogger(Lifecycle.class); public static String XCHAIN_CONFIG = "META-INF/xchain-config.xml"; private static ConfigDocumentContext configDocumentContext = null; private static LifecycleContext context = null; private static List<LifecycleStep> lifecycleStepList = null; private static boolean saxParserFactoryCreated = false; private static boolean transformerFactoryCreated = false; private static boolean documentBuilderFactoryCreated = false; private static boolean xalanFactoryCreated = false; private static boolean xsltcFactoryCreated = false; private static boolean saxonFactoryCreated = false; private static boolean joostFactoryCreated = false; private static Converter oldQNameConverter = null; /** * Start the lifecycle. Loading all lifecycle steps found by the LifecycleStepScanner. * * @see LifecycleStepScanner */ public static void startLifecycle() throws LifecycleException { try { ThreadLifecycle.getInstance().getCCLPolicy().bindCCL(); synchronized (Lifecycle.class) { // if xchains is currently running, then throw a lifecycle exception. if (isRunning()) { throw new LifecycleException("Start Lifecycle called while xchains is running."); } try { // create a new Lifecycle context. context = new LifecycleContext(); context.setClassLoader( new LifecycleClassLoader(Thread.currentThread().getContextClassLoader())); // scan for the lifecycle listeners. startLifecycleSteps(); // Clear the scanner cache now that all the lifecycles are done using it. ScannerLifecycle.getInstance().clearCache(); } catch (LifecycleException le) { context = null; throw le; } catch (Throwable t) { context = null; throw new LifecycleException("Could not start the lifecycle due to a throwable.", t); } } } finally { ThreadLifecycle.getInstance().getCCLPolicy().unbindCCL(); } } /** * Shutdown the lifecycle. */ public static void stopLifecycle() throws LifecycleException { synchronized (Lifecycle.class) { // stop the lifecycle steps. stopLifecycleSteps(); // remove the context to null. context = null; } } /** * Restart the lifecycle. This effectively shuts down all current lifecycle steps and restarts the lifecycle. */ public static void restartLifecycle() throws LifecycleException { synchronized (Lifecycle.class) { stopLifecycle(); startLifecycle(); } } /** * Returns true if the life cycle */ public static boolean isRunning() { synchronized (Lifecycle.class) { return context != null; } } /** * Returns the current LifecycleContext. Null if the Lifecycle is not running. */ public static LifecycleContext getLifecycleContext() { return context; } /** * Start all lifecycle steps found by the LifecycleStepScanner. * * @throws LifecycleException If an exception was encountered attempting to start the lifecycle steps. * @see LifecycleStepScanner */ private static void startLifecycleSteps() throws LifecycleException { // Create a scanner to find the lifecycle steps in the current classpath. LifecycleStepScanner scanner = new LifecycleStepScanner(context); try { scanner.scan(); lifecycleStepList = scanner.getLifecycleStepList(); } catch (ScanException se) { throw new LifecycleException("An exception was thrown while scanning for lifecycle steps.", se); } if (log.isInfoEnabled()) { StringBuilder message = new StringBuilder(); message.append("Found ").append(lifecycleStepList.size()).append(" lifecycle steps:\n"); for (LifecycleStep lifecycleStep : lifecycleStepList) { message.append(" ").append(lifecycleStep.getQName()).append("\n"); } log.info(message.toString()); } // Start all the found lifecycle steps. ListIterator<LifecycleStep> iterator = lifecycleStepList.listIterator(); try { while (iterator.hasNext()) { LifecycleStep lifecycleStep = iterator.next(); if (log.isInfoEnabled()) { log.info("Starting Lifecycle Step '" + lifecycleStep.getQName() + "'."); } lifecycleStep.startLifecycle(context, Lifecycle.configDocumentContext); if (log.isInfoEnabled()) { log.info("Finished Lifecycle Step '" + lifecycleStep.getQName() + "'."); } } } catch (LifecycleException le) { if (log.isErrorEnabled()) { log.error("Stopping the lifecycle startup due to a lifecycle exception.", le); } iterator.previous(); while (iterator.hasPrevious()) { LifecycleStep lifecycleStep = iterator.previous(); try { lifecycleStep.stopLifecycle(context); } catch (Throwable t) { if (log.isWarnEnabled()) { log.warn("An exception was thrown while stopping a lifecycle exception.", t); } } } // clear the lifecycle step list. lifecycleStepList.clear(); // we should throw the lifecycle exception here. throw le; } finally { // make sure the configuration DOM gets garbage collected, no reason to keep it around once everyone is configured. Lifecycle.configDocumentContext = null; } } /** * Stop all current lifecycle steps. The first step to be stopped is the last one started. */ private static void stopLifecycleSteps() { ListIterator<LifecycleStep> iterator = lifecycleStepList.listIterator(lifecycleStepList.size()); while (iterator.hasPrevious()) { LifecycleStep step = iterator.previous(); try { if (log.isInfoEnabled()) { log.info("Stopping Lifecycle Step '" + step.getQName() + "'."); } step.stopLifecycle(context); if (log.isInfoEnabled()) { log.info("Finished Lifecycle Step '" + step.getQName() + "'."); } } catch (Throwable t) { if (log.isWarnEnabled()) { log.warn("An exception was thrown while stopping a lifecycle exception.", t); } } } lifecycleStepList.clear(); } /** * This step creates a ConfigDocumentContext instance which will be passed to all lifecycle start steps, which take a * ConfigDocumentContext, when Lifecycle.startLifecycle() is called. Consequently, all lifecycle steps that take a * ConfigDocumentContext have an implicit dependency upon this step such that they always run after it. * * @throws LifecycleException */ @StartStep(localName = "create-config-document-context", after = { "xml-factory-lifecycle" }) public static void startCreateConfigDocumentContext() throws LifecycleException { Lifecycle.configDocumentContext = createConfigurationContext(XCHAIN_CONFIG); } /** * Lifecycle Step to load the xchain configuration. */ @StartStep(localName = "config", xmlns = { "xmlns:config='http://xchain.org/config/1.0'" }) public static void startConfiguration(LifecycleContext context, ConfigDocumentContext configDocContext) throws MalformedURLException { // Read DOM and set values on configContext ConfigContext configContext = Lifecycle.getLifecycleContext().getConfigContext(); Boolean monitor = (Boolean) configDocContext.getValue("/config:config/config:monitor", Boolean.class); if (monitor != null) configContext.setMonitored(monitor); Integer catalogCacheSize = (Integer) configDocContext.getValue("/config:config/config:catalog-cache-size", Integer.class); if (catalogCacheSize != null) configContext.setCatalogCacheSize(catalogCacheSize); Integer templateCacheSize = (Integer) configDocContext .getValue("/config:config/config:templates-cache-size", Integer.class); if (templateCacheSize != null) configContext.setTemplatesCacheSize(templateCacheSize); addUrls(configDocContext, "/config:config/config:resource-base-url/@config:system-id", configContext.getResourceUrlList()); addUrls(configDocContext, "/config:config/config:source-base-url/@config:system-id", configContext.getSourceUrlList()); addUrls(configDocContext, "/config:config/config:webapp-base-url/@config:system-id", configContext.getWebappUrlList()); // configure the URLFactory for file monitoring if requested if (configContext.isMonitored()) { if (log.isDebugEnabled()) { log.debug("Config: Monitoring is enabled, configuring URL translation strategies..."); } // configure the resource protocol 'context-class-loader' authority for monitoring if (!configContext.getResourceUrlList().isEmpty()) { CompositeUrlTranslationStrategy contextClassLoaderStrategy = new CompositeUrlTranslationStrategy(); for (Iterator<URL> it = configContext.getResourceUrlList().iterator(); it.hasNext();) { URL baseUrl = it.next(); BaseUrlUrlTranslationStrategy baseUrlStrategy = new BaseUrlUrlTranslationStrategy(baseUrl, BaseUrlUrlTranslationStrategy.URL_FACTORY_URL_SOURCE); contextClassLoaderStrategy.getTranslatorList().add(baseUrlStrategy); if (log.isDebugEnabled()) { log.debug(" Adding resource URL: " + baseUrl); } } // now add the standard context class loader strategy contextClassLoaderStrategy.getTranslatorList().add(new ContextClassLoaderUrlTranslationStrategy()); // override the standard strategy with the new composite strategy ResourceUrlConnection.registerUrlTranslationStrategy( ResourceUrlConnection.CONTEXT_CLASS_LOADER_ATHORITY, contextClassLoaderStrategy); } } } /** * The lifecycle step that engineers command classes. This step creates a ClassScanner for the context's class loader and * calls its scan method. * * @see org.xchain.framework.lifecycle.ClassScanner */ @StartStep(localName = "command-engineering", after = { "config" }) public static void startCommandEngineering(LifecycleContext context) { // for each class that is a Catalog or Command, create an entry for those in the context. ClassScanner classScanner = new ClassScanner(context); classScanner.scan(); } /** * Calls XmlFactoryLifecycle.startLifecycle( ... ). * * @param context * @throws LifecycleException */ @StartStep(localName = "xml-factory-lifecycle", before = { "config" }) public static void startXmlFactory(LifecycleContext context) throws LifecycleException { XmlFactoryLifecycle.startLifecycle(context); } /** * Calls XmlFactoryLifecycle.stopLifecycle( ... ). * * @param context */ @StopStep(localName = "xml-factory-lifecycle") public static void stopXmlFactory(LifecycleContext context) { XmlFactoryLifecycle.stopLifecycle(context); } /** * Sets the default SAX, XSLT, and DOM implementations to use on the XmlFactoryLifecycle for those that are not * already set. To override any of these defaults, create a chain that runs before this one and sets the factories as * desired on the XmlFactoryLifecycle. * * @param context */ @StartStep(localName = "default-xml-factory", before = { "xml-factory-lifecycle" }) public static void startDefaultXmlFactory(LifecycleContext context) { // add the default xml parser factory step. if (getSaxParserFactoryFactory(DEFAULT_SAX_PARSER_FACTORY_NAME) == null) { saxParserFactoryCreated = true; putSaxParserFactoryFactory(DEFAULT_SAX_PARSER_FACTORY_NAME, new DefaultSaxParserFactoryFactory()); } // add the default xml transformer factory. if (getTransformerFactoryFactory(DEFAULT_TRANSFORMER_FACTORY_NAME) == null) { transformerFactoryCreated = true; putTransformerFactoryFactory(DEFAULT_TRANSFORMER_FACTORY_NAME, new DefaultTransformerFactoryFactory()); } // add the default document builder factory if (getDocumentBuilderFactory(DEFAULT_DOCUMENT_BUILDER_FACTORY_NAME) == null) { documentBuilderFactoryCreated = true; putDocumentBuilderFactoryFactory(DEFAULT_DOCUMENT_BUILDER_FACTORY_NAME, new DefaultDocumentBuilderFactoryFactory()); } // add the default xml transformer factory. if (getTransformerFactoryFactory(XALAN_FACTORY_NAME) == null && factoryClassExists(XALAN_FACTORY_NAME, XALAN_FACTORY_CLASS_NAME)) { xalanFactoryCreated = true; putTransformerFactoryFactory(XALAN_FACTORY_NAME, new BasicTransformerFactoryFactory(XALAN_FACTORY_CLASS_NAME)); } // add the default xml transformer factory. if (getTransformerFactoryFactory(XSLTC_FACTORY_NAME) == null && factoryClassExists(XSLTC_FACTORY_NAME, XSLTC_FACTORY_CLASS_NAME)) { xsltcFactoryCreated = true; putTransformerFactoryFactory(XSLTC_FACTORY_NAME, new BasicTransformerFactoryFactory(XSLTC_FACTORY_CLASS_NAME)); } // add the default xml transformer factory. if (getTransformerFactoryFactory(SAXON_FACTORY_NAME) == null && factoryClassExists(SAXON_FACTORY_NAME, SAXON_FACTORY_CLASS_NAME)) { saxonFactoryCreated = true; putTransformerFactoryFactory(SAXON_FACTORY_NAME, new BasicTransformerFactoryFactory(SAXON_FACTORY_CLASS_NAME)); } if (getTransformerFactoryFactory(JOOST_FACTORY_NAME) == null && factoryClassExists(JOOST_FACTORY_NAME, JOOST_FACTORY_CLASS_NAME)) { joostFactoryCreated = true; putTransformerFactoryFactory(JOOST_FACTORY_NAME, new BasicTransformerFactoryFactory(JOOST_FACTORY_CLASS_NAME)); } } /** * Unsets any defaults set by the default-xml-factory start step. * @param context */ @StopStep(localName = "default-xml-factory") public static void stopDefaultXmlFactory(LifecycleContext context) { // remove the joost transformer factory if it was created by this step. if (joostFactoryCreated) { joostFactoryCreated = false; removeTransformerFactoryFactory(JOOST_FACTORY_NAME); } // remove the default xml transformer factory if it was created by this step. if (saxonFactoryCreated) { saxonFactoryCreated = false; removeTransformerFactoryFactory(SAXON_FACTORY_NAME); } // remove the default xml transformer factory if it was created by this step. if (xsltcFactoryCreated) { xsltcFactoryCreated = false; removeTransformerFactoryFactory(XSLTC_FACTORY_NAME); } // remove the default xml transformer factory if it was created by this step. if (xalanFactoryCreated) { xalanFactoryCreated = false; removeTransformerFactoryFactory(XALAN_FACTORY_NAME); } // remove the default document builder factory if it was created by this step. if (documentBuilderFactoryCreated) { documentBuilderFactoryCreated = false; removeDocumentBuilderFactoryFactory(DEFAULT_DOCUMENT_BUILDER_FACTORY_NAME); } // remove the default xml transformer factory if it was created by this step. if (transformerFactoryCreated) { transformerFactoryCreated = false; removeTransformerFactoryFactory(DEFAULT_TRANSFORMER_FACTORY_NAME); } // remove the default xml parser factory if it was created by this step. if (saxParserFactoryCreated) { saxParserFactoryCreated = false; removeSaxParserFactoryFactory(DEFAULT_SAX_PARSER_FACTORY_NAME); } } /** * Sets up the default conversion objects in the bean utils. */ @StartStep(localName = "default-conversions") public static void startDefaultConversions(LifecycleContext lifecycleContext) { oldQNameConverter = ConvertUtils.lookup(QName.class); ConvertUtils.register(new QNameConverter(), QName.class); } /** * Removes the standard conversion objects in the bean utils. */ @StopStep(localName = "default-conversions") public static void stopDefaultConversions(LifecycleContext lifecycleContext) { if (oldQNameConverter != null) { ConvertUtils.register(oldQNameConverter, QName.class); } else { ConvertUtils.deregister(QName.class); } oldQNameConverter = null; } /** * Creates a new ConfigContext of the DOM produced by parsing the indicated resource. * * @throws IOException * @throws SAXException * @throws ParserConfigurationException */ private static ConfigDocumentContext createConfigurationContext(String resource) throws LifecycleException { try { Object contextBean = null; URL configUrl = Thread.currentThread().getContextClassLoader().getResource(resource); if (configUrl == null) { // we can't find the config so we just an instance of Object for the ConfigDocumentContext. if (log.isDebugEnabled()) log.debug("Cannot find configuration at resource '" + resource + "'."); contextBean = new Object(); } else { // we have a config, create a DOM out of it and use it for the ConfigDocumentContext. if (log.isDebugEnabled()) { log.debug("Loading xchain config file for url: " + configUrl.toExternalForm()); } // get the document builder. DocumentBuilder documentBuilder = XmlFactoryLifecycle.newDocumentBuilder(); InputSource configInputSource = UrlSourceUtil.createSaxInputSource(configUrl); Document document = documentBuilder.parse(configInputSource); contextBean = document; } // create the context ConfigDocumentContext configDocumentContext = new ConfigDocumentContext(null, contextBean, Scope.chain); configDocumentContext.setConfigUrl(configUrl); configDocumentContext.setLenient(true); return configDocumentContext; } catch (Exception e) { throw new LifecycleException("Error loading configuration from resource '" + resource + "'.", e); } } private static void addUrls(ConfigDocumentContext configDocContext, String urlxpath, List<URL> urlList) throws MalformedURLException { Iterator<?> urlStringIterator = configDocContext.iterate(urlxpath); while (urlStringIterator.hasNext()) { String urlString = urlStringIterator.next().toString(); if (!"".equals(urlString)) { urlList.add(new URL(urlString)); } } } private static boolean factoryClassExists(QName factoryName, String className) { boolean result = false; try { Thread.currentThread().getContextClassLoader().loadClass(className); result = true; } catch (ClassNotFoundException cnfe) { if (log.isInfoEnabled()) { log.info("Not creating default transformer factory for '" + factoryName + "' because its driver class '" + className + "' is not in the context class loader."); } } return result; } }