/** * 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; } }