/**
 * 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 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.axis2.jaxws;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.HttpURLConnection;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.ws.Binding;

import org.apache.axiom.om.util.UUIDGenerator;
import org.apache.axis2.Constants;
import org.apache.axis2.addressing.AddressingHelper;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.context.OperationContext;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.TransportInDescription;
import org.apache.axis2.description.TransportOutDescription;
import org.apache.axis2.engine.AxisEngine;
import org.apache.axis2.jaxws.registry.FactoryRegistry;
import org.apache.axis2.jaxws.server.JAXWSMessageReceiver;
import org.apache.axis2.jaxws.server.endpoint.lifecycle.factory.EndpointLifecycleManagerFactory;
import org.apache.axis2.transport.RequestResponseTransport;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.axis2.transport.http.HTTPTransportUtils;
import org.apache.axis2.transport.http.TransportHeaders;
import org.apache.axis2.util.MessageContextBuilder;
import org.ow2.easybeans.container.session.stateless.StatelessSessionFactory;
import org.ow2.jonas.ws.axis2.easybeans.Axis2EjbMessageReceiver;
import org.ow2.jonas.ws.axis2.easybeans.Axis2EndpointLifecycleManagerFactory;
import org.ow2.jonas.ws.axis2.http.Axis2RequestResponseTransport;
import org.ow2.jonas.ws.axis2.http.Axis2TransportInfo;
import org.ow2.jonas.ws.axis2.http.AxisServiceGenerator;
import org.ow2.jonas.ws.axis2.http.WSDLQueryHandler;
import org.ow2.jonas.ws.jaxws.IWSRequest;
import org.ow2.jonas.ws.jaxws.IWSResponse;
import org.ow2.jonas.ws.jaxws.IWebServiceEndpoint;
import org.ow2.jonas.ws.jaxws.PortMetaData;
import org.ow2.jonas.ws.jaxws.WSException;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;

public class Axis2WSEndpoint implements IWebServiceEndpoint {

    private static final Log LOG = LogFactory.getLog(Axis2WSEndpoint.class);

    protected ConfigurationContext configurationContext;

    protected AxisService service;

    protected Binding binding;

    protected PortMetaData portMetaData;

    protected Class endpointClass;

    protected WSDLQueryHandler wsdlQueryHandler;

    protected StatelessSessionFactory factory;

    /**
     * Describe the WS type (EJB/POJO).
     */
    private IWebServiceEndpoint.EndpointType type;

    public Axis2WSEndpoint(final Class endpointClass, IWebServiceEndpoint.EndpointType type, final PortMetaData portMetaData,
            final StatelessSessionFactory factory) {
        this.endpointClass = endpointClass;
        this.portMetaData = portMetaData;
        this.type = type;
        this.factory = factory;
    }

    public void init() throws Exception {

        configurationContext = ConfigurationContextFactory.createBasicConfigurationContext("META-INF/axis2.xml");
        AxisServiceGenerator serviceGen = createServiceGenerator();

        // TODO: Get webservice from wsdl
        service = serviceGen.getServiceFromClass(this.endpointClass);
        service.setScope(Constants.SCOPE_APPLICATION);
        configurationContext.getAxisConfiguration().addService(service);
        this.wsdlQueryHandler = new WSDLQueryHandler(this.service);
        if (this.type == IWebServiceEndpoint.EndpointType.EJB) {
            this.configurationContext.setContextRoot(this.portMetaData.getContextRoot());
            this.configurationContext.setServicePath(this.portMetaData.getUrlPattern());
        }

        FactoryRegistry.setFactory(EndpointLifecycleManagerFactory.class, new Axis2EndpointLifecycleManagerFactory());
    }

    protected AxisServiceGenerator createServiceGenerator() {
        AxisServiceGenerator serviceGenerator = null;
        if (this.type == IWebServiceEndpoint.EndpointType.POJO)
            serviceGenerator = new AxisServiceGenerator();
        else if (this.type == IWebServiceEndpoint.EndpointType.EJB) {
            serviceGenerator = new AxisServiceGenerator();
            Axis2EjbMessageReceiver messageReceiver = new Axis2EjbMessageReceiver(this, this.endpointClass, this.factory);
            serviceGenerator.setMessageReceiver(messageReceiver);
        }
        return serviceGenerator;
    }

    public EndpointType getType() {
        return this.type;
    }

    public void invoke(final IWSRequest request, final IWSResponse response) throws WSException {
        MessageContext msgContext = new MessageContext();
        msgContext.setIncomingTransportName(Constants.TRANSPORT_HTTP);
        msgContext.setProperty(MessageContext.REMOTE_ADDR, request.getRemoteAddr());

        try {
            TransportOutDescription transportOut = this.configurationContext.getAxisConfiguration().getTransportOut(
                    Constants.TRANSPORT_HTTP);
            TransportInDescription transportIn = this.configurationContext.getAxisConfiguration().getTransportIn(
                    Constants.TRANSPORT_HTTP);

            msgContext.setConfigurationContext(this.configurationContext);
            msgContext.setTransportIn(transportIn);
            msgContext.setTransportOut(transportOut);
            msgContext.setServiceGroupContextId(UUIDGenerator.getUUID());
            msgContext.setServerSide(true);
            msgContext.setAxisService(this.service);

            doService(request, response, msgContext);
        } catch (Throwable e) {
            String msg = "Exception occurred while trying to invoke service method doService()";
            LOG.error(msg, e);
            try {
                AxisEngine engine = new AxisEngine(this.configurationContext);

                msgContext.setProperty(MessageContext.TRANSPORT_OUT, response.getOutputStream());
                msgContext.setProperty(Constants.OUT_TRANSPORT_INFO, new Axis2TransportInfo(response));

                MessageContext faultContext = MessageContextBuilder.createFaultMessageContext(msgContext, e);
                // If the fault is not going along the back channel we should be
                // 202ing
                if (AddressingHelper.isFaultRedirected(msgContext)) {
                    response.setStatus(HttpURLConnection.HTTP_ACCEPTED);
                } else {
                    response.setStatus(HttpURLConnection.HTTP_INTERNAL_ERROR);
                }
                engine.sendFault(faultContext);
            } catch (Exception ex) {
                if (AddressingHelper.isFaultRedirected(msgContext)) {
                    response.setStatus(HttpURLConnection.HTTP_ACCEPTED);
                } else {
                    response.setStatus(HttpURLConnection.HTTP_INTERNAL_ERROR);
                    response.setHeader(HTTPConstants.HEADER_CONTENT_TYPE, "text/plain");
                    PrintWriter pw = null;
                    try {
                        pw = new PrintWriter(response.getOutputStream());
                    } catch (IOException e1) {
                        // TODO Auto-generated catch block
                        e1.printStackTrace();
                    }
                    ex.printStackTrace(pw);
                    pw.flush();
                    LOG.error(msg, ex);
                }
            }
        }
    }

    protected void doService(final IWSRequest request, final IWSResponse response, MessageContext msgContext) throws Exception {

        processPOSTRequest(request, response, this.service, msgContext);
        // Finalize response
        OperationContext operationContext = msgContext.getOperationContext();
        Object contextWritten = null;
        Object isTwoChannel = null;
        if (operationContext != null) {
            contextWritten = operationContext.getProperty(Constants.RESPONSE_WRITTEN);
            isTwoChannel = operationContext.getProperty(Constants.DIFFERENT_EPR);
        }

        if ((contextWritten != null) && Constants.VALUE_TRUE.equals(contextWritten)) {
            if ((isTwoChannel != null) && Constants.VALUE_TRUE.equals(isTwoChannel)) {
                response.setStatus(HttpURLConnection.HTTP_ACCEPTED);
                return;
            }
            response.setStatus(HttpURLConnection.HTTP_OK);
        } else {
            response.setStatus(HttpURLConnection.HTTP_ACCEPTED);
        }
    }

    // TODO: Get wsdl
    // protected void processGETRequest(final IWSRequest request, final
    // IWSResponse response, AxisService service,
    // MessageContext msgContext) throws Exception {
    // if (request.getRequestURI().getQuery() != null
    // && (request.getRequestURI().getQuery().startsWith("wsdl") ||
    // request.getURI().getQuery().startsWith("xsd"))) {
    // // wsdl or xsd request
    //
    // if (portInfo.getWsdlFile() != null && !portInfo.getWsdlFile().equals(""))
    // {
    // URL wsdlURL = AxisServiceGenerator.getWsdlURL(portInfo.getWsdlFile(),
    // configurationBaseUrl, classLoader);
    // this.wsdlQueryHandler
    // .writeResponse(request.getRequestURI().toString(), wsdlURL.toString(),
    // response.getOutputStream());
    // } else {
    // service.printWSDL(response.getOutputStream());
    // }
    // } else if (AxisServiceGenerator.isSOAP11(service)) {
    // response.setContentType("text/html");
    // PrintWriter pw = new PrintWriter(response.getOutputStream());
    // pw.write("<html><title>Web Service</title><body>");
    // pw.write("Hi, this is '" + service.getName() + "' web service.");
    // pw.write("</body></html>");
    // pw.flush();
    // } else {
    // // REST request
    // setMsgContextProperties(request, response, service, msgContext);
    //
    // String contentType =
    // request.getHeader(HTTPConstants.HEADER_CONTENT_TYPE);
    //
    // msgContext.setTo(new EndpointReference(request.getURI().toString()));
    //
    // msgContext.setProperty(MessageContext.TRANSPORT_OUT,
    // response.getOutputStream());
    // msgContext.setProperty(Constants.OUT_TRANSPORT_INFO, new
    // Axis2TransportInfo(response));
    //
    // InvocationResponse processed = RESTUtil.processURLRequest(msgContext,
    // response.getOutputStream(), contentType);
    //
    // if (!processed.equals(InvocationResponse.CONTINUE)) {
    // response.setStatus(HttpURLConnection.HTTP_OK);
    // String s = HTTPTransportReceiver.getServicesHTML(configurationContext);
    // PrintWriter pw = new PrintWriter(response.getOutputStream());
    // pw.write(s);
    // pw.flush();
    // }
    // }
    // }

    protected void setMsgContextProperties(final IWSRequest request, final IWSResponse response, AxisService service,
            MessageContext msgContext) throws IOException {
        msgContext.setProperty(MessageContext.TRANSPORT_OUT, response.getOutputStream());
        msgContext.setProperty(Constants.OUT_TRANSPORT_INFO, new Axis2TransportInfo(response));
        msgContext.setProperty(RequestResponseTransport.TRANSPORT_CONTROL, new Axis2RequestResponseTransport(response));
        msgContext.setProperty(Constants.Configuration.TRANSPORT_IN_URL, request.getRequestURI().toString());
        msgContext.setIncomingTransportName(Constants.TRANSPORT_HTTP);

        HttpServletRequest servletRequest = (HttpServletRequest) request.getAttribute(HttpServletRequest.class);
        msgContext.setProperty(HTTPConstants.MC_HTTP_SERVLETREQUEST, servletRequest);

        HttpServletResponse servletResponse = (HttpServletResponse) request.getAttribute(HttpServletResponse.class);
        msgContext.setProperty(HTTPConstants.MC_HTTP_SERVLETRESPONSE, servletResponse);

        ServletContext servletContext = (ServletContext) request.getAttribute(ServletContext.class);
        msgContext.setProperty(HTTPConstants.MC_HTTP_SERVLETCONTEXT, servletContext);

        if (servletRequest != null) {
            msgContext.setProperty(MessageContext.TRANSPORT_HEADERS, new TransportHeaders(servletRequest));
        }

        if (this.binding != null) {
            msgContext.setProperty(JAXWSMessageReceiver.PARAM_BINDING, this.binding);
        }
    }

    protected void processPOSTRequest(final IWSRequest request, final IWSResponse response, AxisService service,
            MessageContext msgContext) throws Exception {
        String contentType = request.getHeader(HTTPConstants.HEADER_CONTENT_TYPE);
        String soapAction = request.getHeader(HTTPConstants.HEADER_SOAP_ACTION);
        if (soapAction == null) {
            soapAction = "\"\"";
        }

        ConfigurationContext configurationContext = msgContext.getConfigurationContext();
        configurationContext.fillServiceContextAndServiceGroupContext(msgContext);

        setMsgContextProperties(request, response, service, msgContext);

        HTTPTransportUtils.processHTTPPostRequest(msgContext, request.getInputStream(), response.getOutputStream(),
                contentType, soapAction, request.getRequestURI().getPath());
    }

    public void start() {
        // TODO Auto-generated method stub

    }

    public void stop() {
        // TODO Auto-generated method stub

    }

    public PortMetaData getPortMetaData() {
        // TODO Auto-generated method stub
        // return this.portMetaData;
        return this.portMetaData;
    }

}
