/*
 * MessageExchangeHelper.java
 *
 * Created on February 5, 2006, 9:51 AM
 *
 * To change this template, choose Tools | Options and locate the template under
 * the Source Creation and Management node. Right-click the template and choose
 * Open. You can then make changes to the template in the Source Editor.
 */

package com.sun.enterprise.jbi.serviceengine.util.soap;

import org.w3c.dom.*;

import com.sun.enterprise.deployment.ServiceReferenceDescriptor;
import com.sun.enterprise.deployment.ServiceRefPortInfo;
import com.sun.enterprise.jbi.serviceengine.ServiceEngineException;
import com.sun.enterprise.jbi.serviceengine.comm.MessageSender;
import com.sun.enterprise.jbi.serviceengine.core.JavaEEServiceEngineContext;
import com.sun.enterprise.jbi.serviceengine.core.ServiceEngineEndpoint;
import com.sun.enterprise.jbi.serviceengine.core.EndpointRegistry;
import com.sun.jbi.wsdl11wrapper.*;
import com.sun.logging.LogDomains;
import javax.jbi.messaging.DeliveryChannel;
import javax.jbi.messaging.InOnly;
import javax.jbi.messaging.InOut;
import javax.jbi.messaging.RobustInOnly;
import javax.jbi.messaging.MessageExchange;
import javax.jbi.messaging.MessageExchangeFactory;
import javax.jbi.messaging.NormalizedMessage;
import javax.jbi.messaging.ExchangeStatus;
import javax.jbi.servicedesc.ServiceEndpoint;
import javax.xml.namespace.QName;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.namespace.QName;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import javax.wsdl.*;
import javax.wsdl.factory.*;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.util.*;
import java.io.*;

/**
 * This class helps in normalizing a SOAP message and denormalizing a normalized
 * message into a SOAP Message
 * @author mu125243
 */
public class MessageExchangeHelper {
    
    private static Logger logger =
            LogDomains.getLogger(LogDomains.SERVER_LOGGER);
    
    private MessageExchange messageExchange;
    private ServiceRefPortInfo portInfo;
    /** Creates a new instance of MessageExchangeHelper */
    public MessageExchangeHelper() {
    }
    
    public void setMessageExchange(MessageExchange messageExchange) {
        this.messageExchange = messageExchange;
    }
    
    
    public SOAPMessage denormalizeMessage(boolean inFlag) throws ServiceEngineException {
        validateMessageExchange();
        NormalizedMessage normalizedMsg = null;
        
        if(inFlag) {
            if(isInOutMessageExchange()) {
                InOut inOutExchange = (InOut)messageExchange;
                normalizedMsg = inOutExchange.getInMessage();
                
            } else {
                InOnly inOnlyExchange = (InOnly)messageExchange;
                normalizedMsg = inOnlyExchange.getInMessage();
            }
        } else
            // assumed that it's a inout message
            normalizedMsg = ((InOut)messageExchange).getOutMessage();
        //create inonly or inout message exchange based on the instance
        
        QName operationQName = messageExchange.getOperation();
        String pattern = messageExchange.getPattern().toString();
        
        Operation operation = new Operation(operationQName.getLocalPart(), pattern);
        // DeNormalize response msg to SOAP msg
        
        MessageDenormalizerImpl d = new MessageDenormalizerImpl();
        //Changes by Servesh
        unWrapMessage(normalizedMsg);
        //End
        SOAPWrapper wrapper = d.denormalizeMessage(normalizedMsg,
                operation, !inFlag);
        SOAPMessage message = wrapper.getMessage();
        printSOAPMessage( "Denormalizing in ? "+ inFlag + "message :" , message) ;
        return message;
        
    }
    
    public boolean isInOutMessageExchange() {
        return messageExchange instanceof InOut;
    }
    
    public void normalizeMessage(SOAPMessage soapMessage, boolean inFlag)  throws ServiceEngineException  {
        validateMessageExchange();
        if(soapMessage != null) {
            
            
            printSOAPMessage( "normalizing in ? "+ inFlag + "message :" , soapMessage) ;
            NormalizedMessage normalizedMsg = null;
            Operation operation = null;
            try {
                
                if(inFlag) {
                    // Get operation name from the localpart of the first child
                    // of <env:Body> in SOAP msg
                    operation = getOperation(soapMessage, messageExchange.getService());
                    QName opQName = new QName(messageExchange.getService().getNamespaceURI(), operation.getName());
                    messageExchange.setOperation(opQName);
                    
                }else {
                    operation = new Operation(messageExchange.getOperation().getLocalPart(),
                            messageExchange.getPattern().toString());
                }
                
                normalizedMsg = messageExchange.createMessage();
                //soapMessage.writeTo(System.out);
                SOAPWrapper wrapper = new SOAPWrapper(soapMessage);
                MessageNormalizerImpl normalizer = new MessageNormalizerImpl();
                normalizer.normalizeMessage(wrapper, normalizedMsg, operation);
                //Changes by Servesh
                wrapMessage(normalizedMsg);
                //end of changes
                if(inFlag)
                    ((InOut)messageExchange).setInMessage(normalizedMsg);
                else
                    ((InOut)messageExchange).setOutMessage(normalizedMsg);
            } catch(Exception e) {
                e.printStackTrace();
            }
            
        }
        
    }
    
    private void unWrapMessage(NormalizedMessage normalizedMsg) {
        try {
            String wsdlPath = null;
            String endpointName = null;
            QName serviceName = null;
            ServiceEndpoint serviceEndpoint = messageExchange.getEndpoint();
            if(serviceEndpoint != null) {
                EndpointRegistry endpointRegistry = EndpointRegistry.getInstance();
                endpointName = serviceEndpoint.getEndpointName();
                serviceName = serviceEndpoint.getServiceName();
                ServiceEngineEndpoint serviceEngineEndpoint = endpointRegistry.get(
                        serviceName, endpointName);
                if(serviceEngineEndpoint != null)
                    wsdlPath = serviceEngineEndpoint.getWsdlPath();
            }
            if(wsdlPath == null && portInfo != null) {
                ServiceReferenceDescriptor serviceRef = portInfo.getServiceReference();
                //ServiceRefPortInfo portInfo = serviceRef.getPortInfo("service.web.example.calculator.Calculator"/*sei.getName()*/);
                serviceName = serviceRef.getServiceName();
                endpointName = portInfo.hasWsdlPort() ? portInfo.getWsdlPort().getLocalPart() : portInfo.getName();
                if(serviceRef.getWsdlFileUrl() != null && serviceRef.getWsdlFileUrl().getProtocol().equals("file"))
                    wsdlPath = serviceRef.getWsdlFileUrl().getPath();
                else
                    wsdlPath = serviceRef.getWsdlFileUri();
            }
            Definition mDefinition = getWsdlDefinition(wsdlPath);
            Wsdl11WrapperHelper helper = new Wsdl11WrapperHelper(mDefinition);
            if(isWsdl11(mDefinition)) {
                Source source = normalizedMsg.getContent();
                String operationName = messageExchange.getOperation().getLocalPart();
                Document unwrappedDoc = null;
                if(messageExchange.getRole().equals(MessageExchange.Role.PROVIDER)) {
                    // It is a Request 
                    unwrappedDoc = helper.unwrapMessage(source, serviceName, 
                                    endpointName, 
                                    operationName,
                                    true);
                } else { //Consumer
                    //It is a Response
                    unwrappedDoc = helper.unwrapMessage(source, serviceName, 
                                    endpointName, 
                                    operationName,
                                    false);
                }
                normalizedMsg.setContent(new DOMSource(unwrappedDoc));
            }
        }catch(Exception e) {
            e.printStackTrace();
        }        
    }
    
    private void wrapMessage(NormalizedMessage normalizedMsg) {
        try {
            String wsdlPath = null;
            String endpointName = null;
            QName serviceName = null;
            ServiceEndpoint serviceEndpoint = messageExchange.getEndpoint();
            if(serviceEndpoint != null) {
                EndpointRegistry endpointRegistry = EndpointRegistry.getInstance();
                endpointName = serviceEndpoint.getEndpointName();
                serviceName = serviceEndpoint.getServiceName();
                ServiceEngineEndpoint serviceEngineEndpoint = endpointRegistry.get(
                serviceName, endpointName);
                if(serviceEngineEndpoint != null)
                    wsdlPath = serviceEngineEndpoint.getWsdlPath();
            }
            if(wsdlPath == null && portInfo != null) {
                ServiceReferenceDescriptor serviceRef = portInfo.getServiceReference();
                //ServiceRefPortInfo portInfo = serviceRef.getPortInfo("service.web.example.calculator.Calculator"/*sei.getName()*/);
                serviceName = serviceRef.getServiceName();
                endpointName = portInfo.hasWsdlPort() ? portInfo.getWsdlPort().getLocalPart() : portInfo.getName();
                if(serviceRef.getWsdlFileUrl() != null && serviceRef.getWsdlFileUrl().getProtocol().equals("file"))
                    wsdlPath = serviceRef.getWsdlFileUrl().getPath();
                else
                    wsdlPath = serviceRef.getWsdlFileUri();
            }
            Definition mDefinition = getWsdlDefinition(wsdlPath);
            Wsdl11WrapperHelper helper = new Wsdl11WrapperHelper(mDefinition);
            if(isWsdl11(mDefinition)) {
                Source source = normalizedMsg.getContent();
                //
                /*try {
                    System.out.println("Printing normalized message");
                        javax.xml.transform.Transformer transformer = javax.xml.transform.TransformerFactory.newInstance().newTransformer();
                        //System.out.print(message);
                        javax.xml.transform.stream.StreamResult result = new javax.xml.transform.stream.StreamResult(System.out);
                        transformer.transform(source, result);
                        System.out.println("Printing over");
                    } catch(Exception e) {
                        e.printStackTrace();
                    }
                 **/
                //
                String operationName = messageExchange.getOperation().getLocalPart();
                Document wrappedDoc = null;
                if(messageExchange.getRole().equals(MessageExchange.Role.PROVIDER)) {
                    //It is a Response
                    wrappedDoc = helper.wrapMessage(source, serviceName, 
                                 endpointName, 
                                 operationName,
                                 false);
                } else { //Consumer
                    //It is a Request
                    wrappedDoc = helper.wrapMessage(source, serviceName, 
                                 endpointName, 
                                 operationName,
                                 true);
                }
                normalizedMsg.setContent(new DOMSource(wrappedDoc));
             }
        }catch(Exception e) {
            e.printStackTrace();
        }        
        
    }
    
    private Definition getWsdlDefinition(String wsdlPath) throws Exception{
        javax.wsdl.factory.WSDLFactory mFactory = WSDLFactory.newInstance();
        javax.wsdl.xml.WSDLReader mReader = mFactory.newWSDLReader();
        Definition mDefinition = mReader.readWSDL(wsdlPath);
        return mDefinition;
    }
    
    private boolean isWsdl11(Definition mDefinition) {
        Map map = mDefinition.getNamespaces();
        Collection vals = map.values();
        Iterator itr = vals.iterator();
        while(itr.hasNext()) {
            String ns = (String)itr.next();
            if(ns.startsWith("http://schemas.xmlsoap.org/wsdl/"))
                return true;
        }
        return false;
    }
    
    public MessageExchange getMessageExchange() {
        return messageExchange;
    }
    
    public void initializeMessageExchange(ServiceRefPortInfo portInfo, boolean oneWay)
    throws ServiceEngineException {
        try {
            this.portInfo = portInfo;
            QName svcQName = portInfo.getServiceReference().getServiceName();
            DeliveryChannel channel =
                    JavaEEServiceEngineContext.getInstance(). getDeliveryChannel();
            // Create MessageExchange
            MessageExchangeFactory factory =
                    channel.createExchangeFactoryForService(svcQName);
            
            MessageExchange msgExchange = null;
            NormalizedMessage inMsg = null;
            
            if(oneWay) {
                InOnly inMessageExchange =  factory.createInOnlyExchange();
                inMsg = inMessageExchange.createMessage();
                inMessageExchange.setInMessage(inMsg);
                msgExchange = inMessageExchange;
            } else {
                InOut inOutMessageExchange =  factory.createInOutExchange();
                inMsg = inOutMessageExchange.createMessage();
                inOutMessageExchange.setInMessage(inMsg);
                msgExchange = inOutMessageExchange;
            }
            msgExchange.setService(svcQName);
            setMessageExchange(msgExchange);
        } catch(Exception e) {
            throw new ServiceEngineException(e);
        }
    }
    
    public void handleException(Exception exception) {
        try { 
            if((messageExchange instanceof  InOut) || 
               (messageExchange instanceof RobustInOnly)){
                normalizeException(exception);
            } else if (messageExchange instanceof InOnly) {
                messageExchange.setStatus(ExchangeStatus.ERROR);
            } 
            dispatchMessage();
        } catch(Exception e) {
           logger.log(Level.SEVERE, e.getMessage());
        }
    }
    
    public void handleResponse(SOAPMessage response, boolean flag) {
        try {
            if(messageExchange instanceof  InOut)  {
                normalizeMessage(response, flag);
            } else if((messageExchange instanceof InOnly ) ||
                (messageExchange instanceof RobustInOnly)) {
                messageExchange.setStatus(ExchangeStatus.DONE);
            }
            dispatchMessage();
        } catch(Exception e) {
           logger.log(Level.SEVERE, e.getMessage());
        }
    }
    
    public void normalizeException(Exception exception) {
        if(exception != null) {
            try {
                MessageDenormalizerImpl d = new MessageDenormalizerImpl();
                SOAPWrapper soapWrapper = d.denormalizeMessage(exception);
                normalizeMessage(soapWrapper.getMessage(), false);
                
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    public void dispatchMessage() throws ServiceEngineException {
        if(messageExchange != null) {
            MessageSender messageSender = new MessageSender();
            messageSender.setMessageExchange(messageExchange);
            messageSender.send();
            ServiceEngineException exception = messageSender.getException();
            if(exception != null)
                throw exception;
        }
    }
    
    private void validateMessageExchange() throws ServiceEngineException {
        if(messageExchange == null)
            throw new ServiceEngineException("MessageExchange not set, use setMessageExchange()");
    }
    
    private Operation getOperation(SOAPMessage soapMessage, QName svcQName) throws ServiceEngineException {
        try {
            // Get operation name from the localpart of the first child
            // of <env:Body> in SOAP msg
            SOAPPart sp = soapMessage.getSOAPPart();
            SOAPEnvelope env = sp.getEnvelope();
            SOAPBody body = env.getBody();
            // first child of body is like <ns0:sayHello>
            org.w3c.dom.Node firstChild = body.getFirstChild();
            String namespacePrefix = firstChild.getPrefix();
            String opName = firstChild.getLocalName();
            
            // Get WSDL operation QName. This is in the same namespace as the
            // service, declared in the <definitions> element in the WSDL file.
            String svcNamespace = svcQName.getNamespaceURI();
            
            // namespace URI for body content is the WSDL "types" namespace
            // as in the <schema> element in the WSDL file.
            String namespaceURI = null;
            if(namespacePrefix != null)
                namespaceURI = env.getNamespaceURI(namespacePrefix);
            else
                namespaceURI = svcNamespace;
            
            QName opQName = new QName(svcNamespace, opName);
            
            // Normalize Message
            Operation operation = new Operation(opName, "in-out");
            // TODO Does JAXRPC2.0 allow WSDL 2.0's uri or multipart styles ?
            operation.setStyle("rpc");
            operation.setInputNamespace(namespaceURI);
            operation.setOutputNamespace(namespaceURI);
            return operation;
            
        } catch(Exception e) {
            e.printStackTrace();
            
            throw new ServiceEngineException(e.getMessage());
        }
    }
    
    
    protected void printSOAPMessage(String message, SOAPMessage soapMessage) {
        try {
            if(logger.isLoggable(Level.FINE)) {
                System.out.print(message);
                soapMessage.writeTo(System.out);
            }
        }catch(Exception e) {
            System.out.println(e.getMessage());
        }
    }
    
    protected void printSOAPContent(String message, NormalizedMessage normalizedMessage) {
        if(logger.isLoggable(Level.FINE)) {
            if(normalizedMessage != null) {
                javax.xml.transform.Source source = normalizedMessage.getContent() ;
                if(source != null) {
                    try {
                        javax.xml.transform.Transformer transformer = javax.xml.transform.TransformerFactory.newInstance().newTransformer();
                        System.out.print(message);
                        javax.xml.transform.stream.StreamResult result = new javax.xml.transform.stream.StreamResult(System.out);
                        transformer.transform(source, result);
                    } catch(Exception e) {
                        e.printStackTrace();
                    }
                }
                
            }
        }
    }
    
}
