/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (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/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * Header Notice in each file and include the License file 
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.  
 * If applicable, add the following below the CDDL Header, 
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information: 
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */
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.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.security.jauth.ServerAuthConfig;
import com.sun.enterprise.webservice.WSSCallbackHandler;
import com.sun.enterprise.webservice.monitoring.JAXWSEndpointImpl;

import javax.xml.rpc.handler.MessageContext;
import javax.xml.ws.soap.SOAPBinding;

import com.sun.xml.ws.spi.runtime.RuntimeEndpointInfo;
import com.sun.xml.ws.spi.runtime.SystemHandlerDelegate;
import com.sun.xml.ws.spi.runtime.WSRtObjectFactory;
import com.sun.xml.ws.spi.runtime.WebServiceContext;
import com.sun.enterprise.deployment.EjbDescriptor;
import com.sun.enterprise.deployment.EjbBundleDescriptor;
import com.sun.enterprise.deployment.ResourceReferenceDescriptor;

import com.sun.xml.ws.server.Tie;

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.
 *
 * @author Jerome Dochez
 */
public class EjbRuntimeEndpointInfo {

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

    protected WebServiceEndpoint endpoint;

    protected StatelessSessionContainer container;

    protected Object webServiceEndpointServant;
    
    protected ServerAuthConfig serverAuthConfig;

    protected InvocationManager invManager;
    
    private WSRtObjectFactory rpcFactory = WSRtObjectFactory.newInstance();    
    
    private RuntimeEndpointInfo jaxWSRuntimeInfo = 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();

	try {
	    // merge message security policy from domain.xml and sun-specific
	    // deployment descriptor
	    serverAuthConfig = ServerAuthConfig.getConfig
		(com.sun.enterprise.security.jauth.AuthConfig.SOAP,
		 endpoint.getMessageSecurityBinding(),
		 WSSCallbackHandler.getInstance());
	} catch (com.sun.enterprise.security.jauth.AuthException ae) {
            logger.log(Level.SEVERE, 
		       "EJB Webservice security configuration Failure", ae);
	}
    }

    public Container getContainer() {
        return container;
    }

    public WebServiceEndpoint getEndpoint() {
        return endpoint;
    }

    public ServerAuthConfig getServerAuthConfig() {
        return serverAuthConfig;
    }

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

    public RuntimeEndpointInfo 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 (jaxWSRuntimeInfo==null) {
            synchronized(this) {
                if(jaxWSRuntimeInfo == null) {
                    populateRuntimeEndpointInfo();
                }
            }
        }

        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;
            // XXX JD we used to do it here but now this will have to move until
            // we actually have the msg context        
            // inv.messageContext = msgContext;

            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 ni WebSvcCtxt
            // should be the same one as used in the ejb's interceptors'        
            javax.xml.ws.handler.MessageContext msgCtxt = rpcFactory.createMessageContext();
            jaxWSRuntimeInfo.getWebServiceContext().setMessageContext(msgCtxt);
            inv.setContextData(jaxWSRuntimeInfo.getWebServiceContext());        

            // 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) {
                    // Process handlers
                    (new WsUtil()).configureJAXWSServiceHandlers(endpoint, jaxWSRuntimeInfo);
                    jaxWSRuntimeInfo.init();
                    handlersConfigured=true;
                }
            }
        }
        return jaxWSRuntimeInfo;
    }

    /** 
     * Force initialization of the endpoint runtime information  
     * as well as the handlers injection 
     */ 
    public RuntimeEndpointInfo initRuntimeInfo() throws Exception{ 
        try { 
            return prepareInvocation(true); 
        } finally { 
            invManager.postInvoke(invManager.getCurrentInvocation()); 
        } 
         
    } 
     
    /* 
     * Do NOT call this method directly since it is doing resource injection 
     * it needs access to the jndi env. call the initRuntimeInfo() instead 
     */ 
    private void populateRuntimeEndpointInfo() {      
        // new to create the jax-ws runtime info if necessary
        if (jaxWSRuntimeInfo==null) {
            jaxWSRuntimeInfo = rpcFactory.createRuntimeEndpointInfo();

            jaxWSRuntimeInfo.setPortName(endpoint.getWsdlPort());
            jaxWSRuntimeInfo.setServiceName(endpoint.getServiceName());
            String implClassName = endpoint.getEjbComponentImpl().getEjbClassName();
            try {
                Class clazz = container.getClassLoader().loadClass(implClassName);
                jaxWSRuntimeInfo.setImplementorClass(clazz);
            } catch(Exception cnfe) {
                logger.severe("Cannot load or instanciate " + implClassName);
            }

            jaxWSRuntimeInfo.setImplementor(webServiceEndpointServant);

            // Set webservice context here
            // If the endpoint has a WebServiceContext with @Resource then
            // that has to be used
            WebServiceContext wsc = null;
            EjbDescriptor ejbDesc = endpoint.getEjbComponentImpl();
            Iterator<ResourceReferenceDescriptor> it = ejbDesc.getResourceReferenceDescriptors().iterator();
            while(it.hasNext()) {
                ResourceReferenceDescriptor r = it.next();
                if(r.isWebServiceContext()) {
                    try {
                        javax.naming.InitialContext ic = new javax.naming.InitialContext();
                        wsc = (WebServiceContext) ic.lookup("java:comp/env/" + r.getName());
                        break;
                    } catch (Throwable t) {}//Swallow intentionally
                }
            }
            if(wsc == null) {
                wsc = new WebServiceContextImpl();
            }
            jaxWSRuntimeInfo.setWebServiceContext(wsc);

            // Setting Binding info
            jaxWSRuntimeInfo.setBinding(rpcFactory.createBinding(endpoint.getProtocolBinding()));

            // MTOM setting depends on this 
            WsUtil wsu = new WsUtil();
            wsu.setMtom(jaxWSRuntimeInfo.getBinding(), endpoint);

            // Set wsdl and catalog info
            java.net.URL catalogURL = null;
            File catalogFile = new File(endpoint.getBundleDescriptor().getDeploymentDescriptorDir() +
                    File.separator + "jax-ws-catalog.xml");
            if(catalogFile.exists()) {
                try {
                    catalogURL = catalogFile.toURL();
                } catch(java.net.MalformedURLException mfue) {
                    logger.warning(" Malformed URL " + mfue.getMessage());
                }
            }
            jaxWSRuntimeInfo.setWsdlInfo(endpoint.getWebService().getWsdlFileUrl(),
                    rpcFactory.createEntityResolver(catalogURL));

            JAXWSEndpointImpl endpointImpl = (JAXWSEndpointImpl) 
                endpoint.getExtraAttribute(JAXWSEndpointImpl.NAME);
            SystemHandlerDelegate delegate = 
                JAXWSSystemHandlerDelegateFactory.getEjbDelegate
                    (serverAuthConfig, endpoint,endpointImpl);

            // set the appropriate delegate in the SOAPMessageDispatcher
            if (endpointImpl != null) {
                endpointImpl.setParent(delegate);
                jaxWSRuntimeInfo.getBinding().setSystemHandlerDelegate(endpointImpl);
            } else {
                jaxWSRuntimeInfo.getBinding().setSystemHandlerDelegate(delegate);
            }
        }
    }

    /**
     * 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.postInvoke(inv);
                } else {
                    invManager.postInvoke(inv);
                }
            }
        } catch(Throwable t) {
            logger.log(Level.FINE, "", t);
        }

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

}
