/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2010 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id: WeldLifeCycleListener.java 19316 2010-03-02 16:24:45Z sauthieg $
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.cdi.weld.internal.tomcat;

import java.lang.reflect.InvocationTargetException;

import javax.decorator.Decorator;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.inject.Inject;
import javax.interceptor.Interceptor;
import javax.naming.NamingException;

import org.apache.AnnotationProcessor;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.core.StandardContext;
import org.jboss.weld.bootstrap.api.Bootstrap;
import org.jboss.weld.bootstrap.spi.BeanDeploymentArchive;
import org.jboss.weld.bootstrap.spi.Deployment;
import org.jboss.weld.environment.tomcat.ForwardingAnnotationProcessor;
import org.jboss.weld.manager.api.WeldManager;
import org.jboss.weld.resources.spi.ResourceLoader;
import org.ow2.jonas.cdi.weld.IWeldService;
import org.ow2.jonas.cdi.weld.internal.WeldDeployableInfo;
import org.ow2.jonas.cdi.weld.internal.resource.ApplicationResourceLoader;
import org.ow2.jonas.cdi.weld.internal.standard.CDIAnnotationProcessor;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;

/**
 * A {@code WeldLifeCycleListener} is a Tomcat Context Lifecycle listener.
 * It is in charge of Weld container start-up and shut-down process for a web-app.
 *
 * @author Guillaume Sauthier
 */
public class WeldLifeCycleListener implements LifecycleListener {

    /**
     * Logger.
     */
    private static Log logger = LogFactory.getLog(WeldLifeCycleListener.class);

    /**
     * Used to store the Weld Bootstrap object created during 'start' notification.
     */
    private WeldDeployableInfo info;

    /**
     * Weld service.
     */
    private IWeldService weld;

    /**
     * Deployment instance used to create the Bootstrap.
     */
    private Deployment deployment;

    /**
     * BeanManager for this web application.
     */
    private BeanManager manager;

    /**
     * Construct a WeldLifeCycleListener.
     * @param weld weld service
     * @param deployment Weld deployment instance
     * @param info the Weld deployable infor related to the Deployable instance
     */
    public WeldLifeCycleListener(final IWeldService weld,
                                 final Deployment deployment,
                                 final WeldDeployableInfo info) {
        this.weld = weld;
        this.deployment = deployment;
        this.info = info;
    }

    /**
     * React when an event is fired by the Context.
     * @param event tomcat Context's life-cycle event
     */
    public void lifecycleEvent(final LifecycleEvent event) {

        if (event.getLifecycle() instanceof StandardContext) {
            StandardContext context = (StandardContext) event.getLifecycle();

            if (Lifecycle.START_EVENT.equals(event.getType())) {
                start(context);
            } else if (Lifecycle.AFTER_START_EVENT.equals(event.getType())) {
                afterStart(context);
            } else if (Lifecycle.AFTER_STOP_EVENT.equals(event.getType())) {
                afterStop(context);
            }
        }
    }

    /**
     * Stop the Weld container (if any has been started)
     * @param context Tomcat Context
     */
    private void afterStop(final StandardContext context) {

        // Stop the weld container
        // TODO Move this in the IWeldService interface
        if (info.getBootstrap() != null) {

            logger.debug("Stop Weld container for ''{0}'' ...", context.getName());

            info.getBootstrap().shutdown();

            // Nullify the Bootstrap since there is no way to remove an Info from the Deployable :'(
            info.setBootstrap(null);
        }
    }

    /**
     * Start the Weld container
     * @param context Tomcat Context
     */
    private void start(final StandardContext context) {

        logger.debug("Start Weld container for ''{0}'' ...", context.getName());

        // Provides our Application ClassLoader backed ResourceLoader
        ClassLoader webAppLoader = context.getLoader().getClassLoader();
        ResourceLoader resourceLoader = new ApplicationResourceLoader(webAppLoader);
        deployment.getServices().add(ResourceLoader.class, resourceLoader);

        // TODO Beurk, this HAS to be improved
        performUglyClassLoaderHack(webAppLoader);

        // Start Weld
        Bootstrap boot = weld.startWeldContainer(deployment);

        BeanDeploymentArchive main = deployment.getBeanDeploymentArchives().iterator().next();
        manager = boot.getManager(main);

        // TODO Store the BeanManager in the ENC

        // Store the bootstrap for future use (stop)
        info.setBootstrap(boot);
    }

    /**
     * Wrap the Context {@link AnnotationProcessor} with our own, that can inject CDI beans.
     * @param context Tomcat Context
     */
    private void afterStart(final StandardContext context) {

        // Replace the AnnotationProcessor with our own version
        final AnnotationProcessor original = context.getAnnotationProcessor();
        final AnnotationProcessor cdiAnnotationProcessor = new CDIAnnotationProcessor(manager);

        // Create a forwarding Processor
        AnnotationProcessor processor = new ForwardingAnnotationProcessor() {

            @Override
            protected AnnotationProcessor delegate() {
                return original;
            }

            @Override
            public void processAnnotations(Object instance) throws IllegalAccessException, InvocationTargetException, NamingException {
                // Usual injection mechanism
                super.processAnnotations(instance);
                // CDI Injection
                cdiAnnotationProcessor.processAnnotations(instance);
            }
        };

        // Place the new processor back into the Context
        context.setAnnotationProcessor(processor);
    }

    private void performUglyClassLoaderHack(ClassLoader loader) {

        // TODO Fix me this is a ugly hack to force loading of some packages
        // This is required because the WebApp ClassLoader parent (indirect parent)
        // has a "DynamicImport-Package *". DIP has a something subtle: it seems the
        // wires are not created eagerly.
        // So in order to force the wire creation, we pre-load some classes to unsure
        // that the wire is done.
        try {
            loader.loadClass("org.jboss.interceptor.util.proxy.TargetInstanceProxy");
            loader.loadClass("javassist.util.proxy.ProxyObject");
            loader.loadClass(Decorator.class.getName());
            loader.loadClass(Inject.class.getName());
            loader.loadClass(Interceptor.class.getName());
            loader.loadClass(InjectionPoint.class.getName());
        } catch (ClassNotFoundException e) {
            logger.debug("Cannot load a class from {0}", loader, e);
        }
    }

}
