org.xchain.framework.lifecycle.Lifecycle.java Source code

Java tutorial

Introduction

Here is the source code for org.xchain.framework.lifecycle.Lifecycle.java

Source

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