/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2008 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: WSContextLifecycleListener.java 20050 2010-07-02 13:40:21Z benoitf $
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.web.tomcat7.ws;

import org.apache.catalina.Container;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Wrapper;
import org.apache.catalina.core.StandardContext;
import org.ow2.jonas.ws.jaxws.IJAXWSService;
import org.ow2.jonas.ws.jaxws.IWebServiceEndpoint;
import org.ow2.jonas.ws.jaxws.WSException;
import org.ow2.jonas.ws.jaxws.PortMetaData;
import org.ow2.jonas.ws.jaxws.http.servlet.JAXWSServlet;
import org.ow2.util.ee.metadata.war.api.IWarClassMetadata;

import java.util.Map;

/**
 * React to Context Lifecycle events (start/stop/...).
 * This class acts as the @WebService POJO annotated web service deployer.
 * Right after the Context is started, existing registered servlet (from web.xml)
 * are analyzed, if they marks a web service, the Wrapper class is adapted
 * for web service endpoint invocation.
 *
 * After Context's startup, we benefit of the real WebApp ClassLoader, so
 * we can easily creates our own Wrappers
 * @author Guillaume Sauthier
 */
public class WSContextLifecycleListener implements LifecycleListener {

    /**
     * List of @WebServices annotated classes.
     */
    private Map<String, IWarClassMetadata> services;

    /**
     * The JAXWS Service.
     */
    private IJAXWSService jaxwsService;

    /**
     * Construct a new LifecycleListener.
     * @param services JAX-WS web services description
     * @param jaxws the JAXWS service
     */
    public WSContextLifecycleListener(final Map<String, IWarClassMetadata> services,
                                      final IJAXWSService jaxws) {
        this.services = services;
        this.jaxwsService = jaxws;
    }

    /**
     * Callback for all Context Lifecycle events.
     * We only react to START_EVENT, AFTER_START_EVENT, AFTER_STOP_EVENT
     * @param event the Lifecycle event
     */
    public void lifecycleEvent(final LifecycleEvent event) {
        if (Lifecycle.START_EVENT.equals(event.getType())) {
            onContextStart((StandardContext) event.getLifecycle());

        } else if (Lifecycle.AFTER_START_EVENT.equals(event.getType())) {
            onAfterContextStart((StandardContext) event.getLifecycle());

        } else if (Lifecycle.AFTER_STOP_EVENT.equals(event.getType())) {
            onAfterContextStop((StandardContext) event.getLifecycle());
        }
    }

    /**
     * Called at the real startup of the Context.
     * But before the Wrappers have been started.
     * This is important because, we want to change the wrappers.
     * @param context Tomcat Context
     */
    private void onContextStart(final StandardContext context) {

        // Break processing if there is no services to deploy
        if (services.isEmpty()) {
            return;
        }

        // find all the declared servlets/wrappers
        Container[] childs = context.findChildren();
        // Childrens are all Wrappers
        if (childs != null) {
            for (Container child : childs) {
                Wrapper wrapper = (Wrapper) child;
                String servletClass = wrapper.getServletClass();

                // Check if the servlet-class is one of the services we have recognized before
                IWarClassMetadata metadata = findWebServiceMetadata(servletClass);
                if (metadata != null) {
                    // OK Got it

                    // Change the servlet-class
                    wrapper.setServletClass(JAXWSServlet.class.getName());

                    // And add an InstanceListener to perform some manual injection before any usage
                    wrapper.addInstanceListener(new EndpointInstanceListener(metadata, jaxwsService));

                    // remove the servlet from the list of endpoints to prepare
                    services.remove(servletClass);

                    // Try to force servlet loading
                    // This is important to do that because if the loadOnStartup is not set (or set
                    // to a negative value), the servlet will be loaded during the first call.
                    // During the servlet loading, we create the webservice endpoint and register
                    // it in our WSContainer so that we can start all our services together.
                    // Waiting for the first call is a bit late for us because we start the WS
                    // endpoints at the end of StandardContext startup (before the first call).
                    int loadOnStartup = wrapper.getLoadOnStartup();
                    if (loadOnStartup < 0) {
                        wrapper.setLoadOnStartup(0);
                    }
                } // else next servlet
            }
        }

        // OK, now we have browsed all the servlets that have been declared in the web.xml
        // The metadata remaining in the services Map are discovered endpoint that were
        // not declared in the web.xml
        // They will be processed 'after startup'

    }

    /**
     * Invoked after Context startup.
     * @param context the Context of the webapp
     */
    private void onAfterContextStart(final StandardContext context) {

        // Iterates on the remaining services (thoses that were not declared in the web.xml)
        for (Map.Entry<String, IWarClassMetadata> entry : services.entrySet()) {

            // As the Context is started, we have a ClassLoader
            ClassLoader loader = context.getLoader().getClassLoader();

            // That we can use to create our endpoint
            IWebServiceEndpoint endpoint = null;
            IWarClassMetadata metadata = entry.getValue();
            try {
                endpoint = jaxwsService.createPOJOWebServiceEndpoint(metadata,
                                                                     loader,
                                                                     context.getServletContext());
            } catch (WSException e) {
                throw new RuntimeException("WebServiceEndpoint creation failed", e);
            }

            // Fill in some data about PMD
            PortMetaData pmd = endpoint.getPortMetaData();

            // Store context name
            pmd.setContextRoot(context.getName());

            // Compute the endpoint naive URL
            String url = URLUtils.getEndpointURL(pmd, (Host) context.getParent());
            pmd.setEndpointURL(url);

            // Create a new Wrapper dedicated to WS endpoint
            WebServiceEndpointStandardWrapper wrapper = null;
            String name = metadata.getJClass().getName().replace('/', '.');
            wrapper = new WebServiceEndpointStandardWrapper(endpoint, name);

            // Add the Servlet/Wrapper to the context
            context.addChild(wrapper);

            // Compute the servlet-mapping
            String pattern = pmd.getUrlPattern();
            context.addServletMapping(pattern, wrapper.getServletName());

            // Print infos
            endpoint.displayInfos();

        }
    }

    /**
     * Called after the stopping of the Context.
     * @param context
     */
    private void onAfterContextStop(StandardContext context) {

        jaxwsService.undeployPOJOEndpoints(context.getServletContext());
    }


    /**
     * Find the metadata associated with a given classname.
     * @param servletClass class name
     * @return the metadata of the named class, may return null, if there
     *         is no associated metadata
     */
    private IWarClassMetadata findWebServiceMetadata(final String servletClass) {

        for (Map.Entry<String, IWarClassMetadata> entry : services.entrySet()) {
            if (servletClass.equals(entry.getKey())) {
                return entry.getValue();
            }
        }
        return null;
    }
}
