/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package com.sun.enterprise.webservice;

import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.OutputStreamWriter;
import java.io.OutputStream;
import java.io.InputStream;
import java.io.File;
import java.util.Iterator;
import java.util.Collection;

import java.rmi.Remote;

import com.sun.enterprise.deployment.WebServiceEndpoint;
import com.sun.enterprise.deployment.WebService;
import com.sun.enterprise.Switch;
import com.sun.ejb.Container;
import com.sun.ejb.containers.StatelessSessionContainer;
import com.sun.ejb.Invocation;
import com.sun.enterprise.InvocationManager;

import com.sun.enterprise.webservice.monitoring.JAXWSEndpointImpl;

import javax.xml.rpc.handler.MessageContext;
import javax.xml.ws.soap.SOAPBinding;
import javax.xml.ws.http.HTTPBinding;
import javax.xml.ws.soap.MTOMFeature;
import javax.xml.ws.WebServiceContext;

import com.sun.xml.ws.api.BindingID;
import com.sun.xml.ws.api.WSBinding;
import com.sun.xml.ws.api.server.WSEndpoint;
import com.sun.xml.ws.api.server.Invoker;
import com.sun.xml.ws.api.server.InstanceResolver;
import com.sun.xml.ws.transport.http.servlet.ServletAdapter;
import com.sun.xml.ws.transport.http.servlet.ServletAdapterList;
import com.sun.xml.ws.api.server.SDDocumentSource;
import com.sun.enterprise.deployment.EjbDescriptor;
import com.sun.enterprise.deployment.EjbBundleDescriptor;
import com.sun.enterprise.deployment.ResourceReferenceDescriptor;
import com.sun.enterprise.instance.BaseManager;
import com.sun.enterprise.deployment.InjectionTarget;
import com.sun.enterprise.deployment.phasing.DeploymentServiceUtils;
import com.sun.enterprise.deployment.backend.DeployableObjectType;
import com.sun.enterprise.server.ApplicationServer;

import java.util.logging.Logger;
import java.util.logging.Level;
import com.sun.logging.LogDomains;

/**
 * Runtime dispatch information about one ejb web service
 * endpoint.  This class must support concurrent access,
 * since a single instance will be used for all web
 * service invocations through the same ejb endpoint.
 * <p><b>NOT THREAD SAFE</b>
 * @author Jerome Dochez
 */
public class EjbRuntimeEndpointInfo {

    protected static final Logger logger = 
        LogDomains.getLogger(LogDomains.EJB_LOGGER);

    protected final WebServiceEndpoint endpoint;

    protected final StatelessSessionContainer container;

    protected final Object webServiceEndpointServant;
    
    protected final InvocationManager invManager;
    
    // the variables below are access in non-thread-safe ways
    private ServletAdapter adapter = null;
    private ServletAdapterList adapterList = null;
    
    private WebServiceContextImpl wsCtxt = null;
    private boolean handlersConfigured = false;    
    
    protected EjbMessageDispatcher messageDispatcher = null;

    public EjbRuntimeEndpointInfo(WebServiceEndpoint webServiceEndpoint,
                                  StatelessSessionContainer ejbContainer, 
                                  Object servant) {
                                  
        endpoint = webServiceEndpoint;
        container  = ejbContainer;
        webServiceEndpointServant = servant;

        Switch theSwitch = Switch.getSwitch();
        invManager = theSwitch.getInvocationManager();
    }

    public Container getContainer() {
        return container;
    }

    public WebServiceEndpoint getEndpoint() {
        return endpoint;
    }

    public String getEndpointAddressUri() {
        return endpoint.getEndpointAddressUri();
    }

    public WebServiceContext getWebServiceContext() {
        return wsCtxt;
    }

    public Object prepareInvocation(boolean doPreInvoke)
        throws Exception {

        // For proper injection of handlers, we have to configure handler
        // after invManager.preInvoke but the Invocation.contextData has to be set
        // before invManager.preInvoke. So the steps of configuring jaxws handlers and 
        // init'ing jaxws is done here - this sequence is important
        if (adapter==null) {
            synchronized(this) {
                if(adapter == null) {
                    try {
                        // Set webservice context here
                        // If the endpoint has a WebServiceContext with @Resource then
                        // that has to be used
                        Invocation tmpInv = new Invocation();
                        tmpInv.isWebService = true;
                        tmpInv.container = container;                
                        tmpInv.transactionAttribute = Container.TX_NOT_INITIALIZED;
                        invManager.preInvoke(tmpInv);
                        EjbDescriptor ejbDesc = endpoint.getEjbComponentImpl();
                        Iterator<ResourceReferenceDescriptor> it = ejbDesc.getResourceReferenceDescriptors().iterator();
                        while(it.hasNext()) {
                            ResourceReferenceDescriptor r = it.next();            
                            if(r.isWebServiceContext()) {
                                Iterator<InjectionTarget> iter = r.getInjectionTargets().iterator();
                                boolean matchingClassFound = false;
                                while(iter.hasNext()) {
                                    InjectionTarget target = iter.next();
                                    if(ejbDesc.getEjbClassName().equals(target.getClassName())) {
                                        matchingClassFound = true;
                                        break;
                                    }
                                }
                                if(!matchingClassFound) {
                                    continue;
                                }
                                try {
                                    javax.naming.InitialContext ic = new javax.naming.InitialContext();
                                    wsCtxt = (WebServiceContextImpl) ic.lookup("java:comp/env/" + r.getName());
                                } catch (Throwable t) {
                                    // Swallowed intentionally
                                }
                            }
                        }
                        if(wsCtxt == null) {
                            wsCtxt = new WebServiceContextImpl();
                        }
                    } catch (Throwable t) {
                        logger.severe("Cannot initialize endpoint " + endpoint.getName() + " : error is : " + t.getMessage());
                        return null;
                    } finally {
                        invManager.postInvoke(invManager.getCurrentInvocation());                         
                    }
                }
            }
        }
        
        if(doPreInvoke) {
                // We need to split the preInvoke tasks into stages since handlers
                // need access to java:comp/env and method authorization must take
                // place before handlers are run.  Note that the application 
                // classloader was set much earlier when the invocation first arrived
                // so we don't need to set it here.
                Invocation inv = new Invocation();

                // Do the portions of preInvoke that don't need a Method object.
                inv.isWebService = true;
                inv.container = container;                
                inv.transactionAttribute = Container.TX_NOT_INITIALIZED;

                // If the endpoint has at least one handler, method
                // authorization will be performed by a container-provided handler
                // before any application handler handleRequest methods are called.
                // Otherwise, the ejb container will do the authorization.
                inv.securityPermissions =  Container.SEC_NOT_INITIALIZED;

                // AS per latest spec change, the MessageContext object in WebSvcCtxt
                // should be the same one as used in the ejb's interceptors'        
                inv.setContextData(wsCtxt);
                
                // In all cases, the WebServiceInvocationHandler will do the
                // remaining preInvoke tasks : getContext, preInvokeTx, etc.
                invManager.preInvoke(inv);
        }

        // Now process handlers and init jaxws RI
        if(!handlersConfigured && doPreInvoke) {
            synchronized(this) {
                if(!handlersConfigured) {
                    try {
                        WsUtil wsu = new WsUtil();
                        String implClassName = endpoint.getEjbComponentImpl().getEjbClassName();
                        Class clazz = container.getClassLoader().loadClass(implClassName);

                        // Get the proper binding using BindingID
                        String givenBinding = endpoint.getProtocolBinding();

                        // Get list of all wsdls and schema
                        SDDocumentSource primaryWsdl = null;
                        Collection docs = null;
                        if(endpoint.getWebService().hasWsdlFile()) {
                            BaseManager mgr;
                            if(endpoint.getBundleDescriptor().getApplication().isVirtual()) {
                                mgr = DeploymentServiceUtils.getInstanceManager(DeployableObjectType.EJB);
                            } else {
                                mgr = DeploymentServiceUtils.getInstanceManager(DeployableObjectType.APP);
                            }
                            mgr.refreshConfigContext(ApplicationServer.getServerContext().getConfigContext());
                            String deployedDir = 
                                mgr.getLocation(endpoint.getBundleDescriptor().getApplication().getRegistrationName());
                            File pkgedWsdl = null;
                            if(deployedDir != null) {
                                if(endpoint.getBundleDescriptor().getApplication().isVirtual()) {
                                    pkgedWsdl = new File(deployedDir+File.separator+
                                                endpoint.getWebService().getWsdlFileUri());
                                } else {
                                    pkgedWsdl = new File(deployedDir+File.separator+
                                            endpoint.getBundleDescriptor().getModuleDescriptor().getArchiveUri().replaceAll("\\.", "_") +
                                            File.separator + endpoint.getWebService().getWsdlFileUri());
                                }
                            } else {
                                pkgedWsdl = new File(endpoint.getWebService().getWsdlFileUrl().getFile());
                            }
                            if(pkgedWsdl.exists()) {
                                primaryWsdl = SDDocumentSource.create(pkgedWsdl.toURL());
                                docs = wsu.getWsdlsAndSchemas(pkgedWsdl);
                            }
                        }

                        // Create a Container to pass ServletContext and also inserting the pipe
                        JAXWSContainer container = new JAXWSContainer(null,
                                endpoint);

                        // Get catalog info
                        java.net.URL catalogURL = null;
                        File catalogFile = new File(endpoint.getBundleDescriptor().getDeploymentDescriptorDir() +
                                File.separator + "jax-ws-catalog.xml");
                        if(catalogFile.exists()) {
                            catalogURL = catalogFile.toURL();
                        }

                        // Create Binding and set service side handlers on this binding

                        boolean mtomEnabled = wsu.getMtom(endpoint);
                        WSBinding binding = null;
                        // Only if MTOm is enabled create the Binding with the MTOMFeature
                        if (mtomEnabled) {
                            MTOMFeature mtom = new MTOMFeature(true);
                            binding = BindingID.parse(givenBinding).createBinding(mtom);
                        } else {
                            binding = BindingID.parse(givenBinding).createBinding();
                        }
                        wsu.configureJAXWSServiceHandlers(endpoint, 
                            endpoint.getProtocolBinding(), binding);

                        // Create the jaxws2.1 invoker and use this
                        Invoker inv = new InstanceResolverImpl(clazz).createInvoker();
                        WSEndpoint wsep = WSEndpoint.create(
                                clazz, // The endpoint class
                                false, // we do not want JAXWS to process @HandlerChain
                                new EjbInvokerImpl(clazz, inv, webServiceEndpointServant, wsCtxt), // the invoker
                                endpoint.getServiceName(), // the service QName
                                endpoint.getWsdlPort(), // the port
                                container,
                                binding, // Derive binding
                                primaryWsdl, // primary WSDL
                                docs, // Collection of imported WSDLs and schema
                                catalogURL
                                );

                        String uri = endpoint.getEndpointAddressUri();
                        String urlPattern = uri.startsWith("/") ? uri : "/" + uri;
                        if(urlPattern.indexOf("/", 1) != -1) {
                            urlPattern = urlPattern.substring(urlPattern.indexOf("/", 1));
                        }

                        // All set; Create the adapter
                        if(adapterList == null) {
                            adapterList = new ServletAdapterList();
                        }
                        adapter = adapterList.createAdapter(endpoint.getName(), urlPattern, wsep);
                        handlersConfigured=true;
                    } catch (Throwable t) {
                        logger.severe("Cannot initialize endpoint " + endpoint.getName() + " : error is : " + t.getMessage());
                        t.printStackTrace();
                        adapter = null;                    
                    }
                }
            }
        }
        return adapter;
    }

    /** 
     * Force initialization of the endpoint runtime information  
     * as well as the handlers injection 
     */ 
    public void initRuntimeInfo(ServletAdapterList list) throws Exception{ 
        try { 
            this.adapterList = list;
            prepareInvocation(true); 
        } finally { 
            invManager.postInvoke(invManager.getCurrentInvocation()); 
        } 
         
    } 
     
    /**
     * Called after attempt to handle message.  This is coded defensively
     * so we attempt to clean up no matter how much progress we made in
     * getImplementor.  One important thing is to complete the invocation
     * manager preInvoke().
     */
    public void releaseImplementor() {
        try {
            Invocation inv = (Invocation) invManager.getCurrentInvocation();

            // Only use container version of postInvoke if we got past
            // assigning an ejb instance to this invocation.  This is
            // because the web service invocation does an InvocationManager
            // preInvoke *before* assigning an ejb instance.  So, we need
            // to ensure that InvocationManager.postInvoke is always
            // called.  It was cleaner to keep this logic in this class
            // and WebServiceInvocationHandler rather than change the
            // behavior of BaseContainer.preInvoke and 
            // BaseContainer.postInvoke.

            if( inv != null ) {
                if( inv.ejb != null ) {
                    container.webServicePostInvoke(inv);
                } else {
                    invManager.postInvoke(inv);
                }
            }
        } catch(Throwable t) {
            logger.log(Level.FINE, "", t);
        }

    }
    
    public EjbMessageDispatcher getMessageDispatcher() {
        if (messageDispatcher==null) {
            messageDispatcher = new Ejb3MessageDispatcher();            
        }
        return messageDispatcher;
    }

}
