/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2009 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 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.ws.cxf;

import java.util.ArrayList;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.xml.namespace.QName;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.handler.PortInfo;

import org.apache.cxf.BusException;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.endpoint.EndpointException;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.frontend.WSDLGetInterceptor;
import org.apache.cxf.jaxws.JaxWsServerFactoryBean;
import org.apache.cxf.jaxws.support.JaxWsEndpointImpl;
import org.apache.cxf.jaxws.support.JaxWsServiceFactoryBean;
import org.ow2.jonas.ws.cxf.util.WebServiceContextInjectionHandler;
import org.ow2.jonas.ws.cxf.jaxws.CXFWSEndpoint;
import org.ow2.jonas.ws.cxf.jaxws.WebservicesContainer;
import org.ow2.jonas.ws.cxf.wsdl.ContainerWsdlInterceptor;
import org.ow2.jonas.ws.jaxws.util.InstanceProcessor;
import org.ow2.jonas.ws.jaxws.handler.PostConstructProcessor;
import org.ow2.jonas.ws.jaxws.handler.builder.AnnotationHandlerChainBuilder;
import org.ow2.jonas.ws.jaxws.handler.builder.HandlerChainBuilder;
import org.ow2.util.annotation.processor.DefaultAnnotationProcessor;
import org.ow2.util.annotation.processor.IAnnotationProcessor;
import org.ow2.util.annotation.processor.ProcessorException;
import org.ow2.util.execution.ExecutionResult;
import org.ow2.util.execution.IExecution;
import org.ow2.util.execution.helper.RunnableHelper;

/**
 * The JOnASJaxwsServerFactoryBean is responsible to build the handler chain
 * associated with the endpoint.
 *
 * @author Guillaume Sauthier
 */
public class JOnASJaxWsServerFactoryBean extends JaxWsServerFactoryBean {

    /**
     * Handler Chain Builder.
     */
    private HandlerChainBuilder handlerChainBuilder;

    /**
     * Group all the endpoints defined in a unique Wsdl.
     */
    private WebservicesContainer<? extends CXFWSEndpoint> container;

    protected Context compEnvContext = null;

    /**
     * Constructs a new ServerFactory using the given ServiceFactory.
     *
     * @param serviceFactory wrapped ServiceFactory
     */
    public JOnASJaxWsServerFactoryBean(final JaxWsServiceFactoryBean serviceFactory,
            final WebservicesContainer<? extends CXFWSEndpoint> container) {
        super(serviceFactory);
        this.container = container;

        // Disengage Handler's building in CXF
        this.doInit = false;
    }

    public void setHandlerChainBuilder(final HandlerChainBuilder builder) {
        this.handlerChainBuilder = builder;
    }

    @Override
    public Server create() {
        // Override classloader as SoapBindingUtil will use Thread Context
        // classloader in order to create proxies.
        // We're setting Context classloader to CXF classloader
        // http://jira.ow2.org/browse/JONAS-369
        ClassLoader old = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader(JaxWsServiceFactoryBean.class.getClassLoader());
            Server server = super.create();

            // Cache java:comp/env context as new InitialContext() may be long (if not already set by EasyBeans
            if (compEnvContext == null) {
                compEnvContext = this.getCompEnvContext();
            }
            // inject resources on service bean
            // Not called by super.create() as this.doInit is set to false
            this.injectResources(this.getServiceBean());

            // Init the handlers with our own builders
            buildHandlerChain();

            return server;
        } finally {
            Thread.currentThread().setContextClassLoader(old);
        }

    }

    /**
     * Build the handler chain.
     */
    private void buildHandlerChain() {

        // Init the builder if required
        if (handlerChainBuilder == null) {
            // By default, use a annotation based builder
            this.handlerChainBuilder = new AnnotationHandlerChainBuilder(getServiceBeanClass());
        }

        // Identify the target port
        final JaxWsServiceFactoryBean sf = (JaxWsServiceFactoryBean) getServiceFactory();
        PortInfo portInfo = new PortInfo() {

            public QName getServiceName() {
                return sf.getServiceQName();
            }

            public QName getPortName() {
                return sf.getEndpointInfo().getName();
            }

            public String getBindingID() {
                return getBindingId();
            }
        };

        // Build the chain using annotations
        List<Handler> chain = new ArrayList<Handler>(handlers);

        // Add the Handlers in the List
        chain.addAll(handlerChainBuilder.buildHandlerChain(portInfo, getHandlerClassLoader()));

        // Init the Handlers
        for (Handler h : chain) {
            // Inject resources
            injectInstance(h);
            // Call @PostConstruct
            constructInstance(h);
        }

        // Assign them to the Binding
        ((JaxWsEndpointImpl) getServer().getEndpoint()).getJaxwsBinding().setHandlerChain(chain);

    }

    /**
     * @return the classloader to be used for Handler loading
     */
    protected ClassLoader getHandlerClassLoader() {
        return this.getServiceBeanClass().getClassLoader();
    }

    /**
     * Manage handler lifecycle.
     *
     * @param handler object to be started
     */
    private void constructHandler(final Handler handler) {
        IAnnotationProcessor processor = new DefaultAnnotationProcessor();
        processor.addAnnotationHandler(new PostConstructProcessor());

        try {
            processor.process(handler);
        } catch (ProcessorException e) {
            throw new WebServiceException("Cannot start handler", e);
        }
    }

    protected void injectResources(Object instance) {
        // Inject resources only for the service bean
        if (instance == this.getServiceBean()) {
            this.injectInstance(instance);
            this.constructInstance(instance);
        }
    }

    /**
     * Manage instance resource injection.
     *
     * @param instance injected object.
     */
    private void injectInstance(final Object instance) {
        // Execute it under Bus' ClassLoader as TCCL to unwrap CAROL Remote References
        IExecution<Void> execution = new IExecution<Void>() {
            @Override
            public Void execute() throws Exception {
                IAnnotationProcessor processor = new DefaultAnnotationProcessor();
                processor.addAnnotationHandler(new InstanceProcessor(compEnvContext));
                processor.addAnnotationHandler(new WebServiceContextInjectionHandler());
                processor.process(instance);
                return null;
            }
        };
        ExecutionResult<Void> result =
                new RunnableHelper<Void>().execute(this.container.getBus().getExtension(ClassLoader.class), execution);

        if (result.hasException()) {
            throw new WebServiceException("Cannot inject resources into '" + instance + "'", result.getException());
        }
    }

    /**
     * Manage instance lifecycle.
     */
    private void constructInstance(final Object instance) {
        IAnnotationProcessor processor = new DefaultAnnotationProcessor();
        processor.addAnnotationHandler(new PostConstructProcessor());

        try {
            processor.process(instance);
        } catch (ProcessorException e) {
            throw new WebServiceException("Cannot start instance '" + instance + "'", e);
        }
    }

    @Override
    protected Endpoint createEndpoint() throws BusException, EndpointException {
        Endpoint endpoint = super.createEndpoint();

        int index = endpoint.getInInterceptors().indexOf(WSDLGetInterceptor.INSTANCE);
        if (index != -1) {
            // Replace the old wsdl interceptor
            endpoint.getInInterceptors().add(index, new ContainerWsdlInterceptor(container));
        }

        return endpoint;
    }

    /**
     * Gets java:comp/env used for resources lookup
     * @return the java:comp/env Context
     */
    private Context getCompEnvContext() {
        IExecution<Context> execution = new IExecution<Context>() {
            @Override
            public Context execute() throws Exception {
                return (Context) new InitialContext().lookup("java:comp/env");
            }
        };
        ExecutionResult<Context> result =
                new RunnableHelper<Context>().execute(this.container.getBus().getExtension(ClassLoader.class), execution);

        if (result.hasException()) {
            throw new WebServiceException("Cannot get java:comp/env context", result.getException());
        }
        return result.getResult();
    }
}
