// Copyright (c) 2004-2005 Sun Microsystems Inc., All Rights Reserved.

/*
 * MessageNormalizerImpl.java
 *
 * SUN PROPRIETARY/CONFIDENTIAL.
 * This software is the proprietary information of Sun Microsystems, Inc.
 * Use is subject to license terms.
 *
 */
package com.sun.enterprise.jbi.serviceengine.util.soap;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.StringTokenizer;

import java.net.HttpURLConnection;

import javax.jbi.JBIException;
import javax.jbi.messaging.NormalizedMessage;
import javax.jbi.messaging.MessagingException;

import javax.xml.soap.Detail;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.soap.SOAPFault;
import javax.xml.soap.AttachmentPart;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.Name;

import javax.activation.DataHandler;

import org.w3c.dom.Document;
import org.w3c.dom.DOMException;
import org.w3c.dom.NodeList;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.Source;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
/**
 * This implementation converts a SOAP request message to a JBI specific format which can
 * be understood by other JBI components.
 *
 * @author Sun Microsystems, Inc.
 */
public class MessageNormalizerImpl implements MessageNormalizer
{
    /**
     * Internal handle to the logger instance
     */
    private Logger mLogger;

    /**
     * Internal handle to String Translator instance.
     */
    private StringTranslator mStringTranslator;

    /**
     * Creates a new instance of MessageNormalizerImpl.
     *
     */
    public MessageNormalizerImpl()
    {
        mLogger = Logger.getLogger(this.getClass().getPackage().getName());
        mStringTranslator = new StringTranslator(this.getClass().getPackage().getName(), this.getClass().getClassLoader());
    }

    /**
     * Converts a SOAP Message to a NormalizedMessage format.
     *
     * @param soapWrapper request message.
     * @param normalizedMessage jbi specific format.
     * @param operation operation requested.
     *
     * @throws JBIException if the message cannot be normalized.
     */
    public void normalizeMessage(
        SOAPWrapper soapWrapper, NormalizedMessage normalizedMessage,
        Operation operation)
        throws JBIException
    {
        if ( soapWrapper.getStatus() == HttpURLConnection.HTTP_INTERNAL_ERROR )
        {
            normalizeFaultMessage( soapWrapper, normalizedMessage);
        }
        else
        {
            normalizeResponseMessage( soapWrapper, normalizedMessage, operation);
        }
    }

    /**
     * Converts a SOAP Fault Message to a NormalizedMessage format. 
     *
     * @param soapWrapper request message.
     * @param normalizedMessage jbi specific format.
     *
     * @throws JBIException if the message cannot be normalized.
     */
    public void normalizeFaultMessage(
        SOAPWrapper soapWrapper, NormalizedMessage normalizedMessage)
        throws JBIException
    {

        try
        {
            SOAPMessage soapMessage = soapWrapper.getMessage();

            if (soapMessage != null)
            {
                SOAPPart soapPart = soapMessage.getSOAPPart();
                SOAPEnvelope soapEnvelope = soapPart.getEnvelope();
                SOAPBody soapBody = soapEnvelope.getBody();
                if ( soapBody.hasFault() )
                {
                    // The Message contains a fault detail element.
                    // Propogate the details in the message content.
                    // ,fault string  and fault actor in the message properties.

		    mLogger.fine(mStringTranslator.getString("SBC_FAULT_ELEMENT_FOUND"));

                    SOAPFault soapFault = soapBody.getFault();
                    Detail soapDetail = soapFault.getDetail();
                    if ( soapDetail != null )
                    {
                        normalizedMessage.setContent(
                                            new DOMSource(
                                                getChildElement(soapDetail)));
                        // Populate the SOAP Header into the message context
                        SOAPHeader soapHeader = soapEnvelope.getHeader();

                        if (soapHeader != null)
                        {
                            normalizedMessage.setProperty(
                                SOAPConstants.HEADER_PROPERTY_NAME,
                                soapHeader);
                        }
                        normalizedMessage.setProperty(
                                        SOAPConstants.FAULT_STRING_PROPERTY_NAME,
                                        soapFault.getFaultString());
                        normalizedMessage.setProperty(
                                        SOAPConstants.FAULT_CODE_PROPERTY_NAME,
                                        extractFaultCode( soapFault.getFaultCode()) );
                    }
                    else
                    {
                        // The Message does not contain fault detail. Propogate details
                        // as a JBIException.
                        throw new JBIException( soapFault.getFaultString() );
                    }


                }
                else
                {
                    // this should not happen.
		    mLogger.severe(mStringTranslator.getString("SBC_ALGORITHM_ERROR"));
                }
            }
        }
        catch (SOAPException soapException)
        {
	    mLogger.severe ( mStringTranslator.getString("SBC_NORMALIZE_FAULT_MESSAGE_FAILURE") );

	    mLogger.severe( mStringTranslator.getString("SBC_ERROR_DETAILS") );
            JBIException jbiException =
                    new JBIException(
                       mStringTranslator.getString(
                                     "SBC_NORMALIZE_FAULT_MESSAGE_FAILURE") );
            jbiException.initCause(soapException);
            throw jbiException;
        }
    }


    /**
     * Converts a SOAP Response Message to a JBI NormalizedMessage.
     *
     * @param soapWrapper request message.
     * @param normalizedMessage jbi normalized message.
     * @param operation operation details.
     *
     * @throws JBIException if the message cannot be normalized.
     */
    public void normalizeResponseMessage(
        SOAPWrapper soapWrapper, NormalizedMessage normalizedMessage,
        Operation operation)
        throws JBIException
    {
	mLogger.fine( mStringTranslator.getString("SBC_NORMALIZE_SOAP_MESSAGE") );
        try
        {
            SOAPMessage soapMessage = soapWrapper.getMessage();

            if (soapMessage != null)
            {
                SOAPPart soapPart = soapMessage.getSOAPPart();
                SOAPEnvelope soapEnvelope = soapPart.getEnvelope();
                SOAPBody soapBody = soapEnvelope.getBody();

                // Check whether the soap body has all namespace prefixes resolved.
                // If not resolve them
                // Populate the SOAP body into the message content.
                org.w3c.dom.Node bodyContents = getBodyContentsAsNode(soapBody);
                DOMSource ds = new DOMSource(bodyContents);
                //dump(ds);
                normalizedMessage.setContent(ds);
                       /* extractPayload(soapBody, operation,
                        isFault(soapWrapper.getStatus()))));*/

                // Attach attachments to the normalizedMessage
                normalizeAttachments(soapMessage, normalizedMessage);

                // Populate the SOAP Header into the message context
                SOAPHeader soapHeader = soapEnvelope.getHeader();

                if (soapHeader != null)
                {
                    //normalizedMessage.setProperty(
                    //    SOAPConstants.HEADER_PROPERTY_NAME, soapHeader);
                }
            }

            Iterator messageProperties = soapWrapper.getProperties();

            for (; messageProperties.hasNext();)
            {
                String propertyName = (String) messageProperties.next();
                normalizedMessage.setProperty(
                    propertyName, soapWrapper.getValue(propertyName));
            }
        }
        catch (RuntimeException runtimeException)
        {
            // This should not happen.
	    mLogger.severe ( mStringTranslator.getString("SBC_NORMALIZE_SOAP_MESSAGE_FAILURE_RT_EXP") );

            JBIException jbiException = new JBIException(
                                            mStringTranslator.getString(
                                            "SBC_NORMALIZE_SOAP_MESSAGE_FAILURE") );
            jbiException.initCause(runtimeException);
            throw jbiException;
        }
        catch (SOAPException soapException)
        {
	    mLogger.severe( mStringTranslator.getString("SBC_NORMALIZE_SOAP_MESSAGE_FAILURE") );
	    mLogger.severe( mStringTranslator.getString("SBC_ERROR_DETAILS", soapException.toString()) );

            JBIException jbiException = new JBIException(
                                    mStringTranslator.getString(
                                     "SBC_NORMALIZE_SOAP_MESSAGE_FAILURE") );
            jbiException.initCause(soapException);
            throw jbiException;
        }
        catch(Exception ex)
        {
            mLogger.severe("Some Exception while dumping Source.");
            ex.printStackTrace();
        }

	mLogger.fine( mStringTranslator.getString("SBC_SUCCESS_NORMALISE_SUCCESS") );
    }

    private org.w3c.dom.Node getBodyContentsAsNode(SOAPBody body)
    {
        org.w3c.dom.Node dNode = null;
        try
        {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document mDoc = builder.newDocument();

            Iterator iter2 = body.getChildElements();
            //This code will not work if there are multiple child elements under
            // soap:Body
            while (iter2.hasNext()) {
                javax.xml.soap.Node n = (javax.xml.soap.Node)iter2.next();
                if(n instanceof SOAPElement)
                {
                    dNode = createDOMNodeFromSOAPNode(n, mDoc);
                    //dump(new DOMSource(dNode));
                    break;
                }
            }
        }
        catch(ParserConfigurationException pce)
        {
            pce.printStackTrace();
            return null;
        }
        catch(Exception e)
        {
            e.printStackTrace();
            return null;
        }
        
        return dNode;
        
    }

   /**
    * Creates a DOM Node from a SOAP Node, using the given DOM Document object
    * to own the DOM Node.
    */
    private org.w3c.dom.Node createDOMNodeFromSOAPNode(javax.xml.soap.Node soapNode, Document document)
    throws ParserConfigurationException
    {
    org.w3c.dom.Node result = null;

    // First figure out what type the soapNode is. Unlike DOM nodes, there
    // is no "nodeType" property, so we have to use reflection.
    if (soapNode instanceof SOAPElement)
    {
        SOAPElement soapElement = (SOAPElement) soapNode;
        Name name = soapElement.getElementName();

        // Create the DOM Element.
        if( (name.getURI().length() != 0) && (name.getQualifiedName().length() != 0) )
            result = document.createElementNS(name.getURI(), name.getQualifiedName());
        else if(name.getLocalName() != null)
            result = document.createElement(name.getLocalName());
        else
        {
            //What to do??
        }

        // Iterate through the attributes of the SOAP node and add each one
        // to the DOM Node.
        for (Iterator iter = soapElement.getAllAttributes();iter.hasNext(); )
        {
            Name attrName = (Name) iter.next();
            String attrValue = soapElement.getAttributeValue(attrName);

            // The createAttributeNS method fails if you give it a null URI.
            Attr attribute = null;
            if (attrName.getURI() == null)
                attribute = document.createAttribute(attrName.getQualifiedName());
            else
                attribute = document.createAttributeNS(attrName.getURI(),attrName.getQualifiedName());

            attribute.setValue(attrValue);

            ((Element) result).setAttributeNodeNS(attribute);
        }

        // Iterate through the child elements of the SOAP node, recursing
        // on this method to add the child SOAP node to the newly created
        // DOM node.
        for (Iterator iter = soapElement.getChildElements(); iter.hasNext(); )
        {
            javax.xml.soap.Node childSOAPNode = (javax.xml.soap.Node) iter.next();
            appendSOAPNodeToDOMNode(document, result, childSOAPNode);
        }
    }
    else if (soapNode instanceof javax.xml.soap.Text)
    {
        javax.xml.soap.Text textNode = (javax.xml.soap.Text) soapNode;
        String textValue = textNode.getValue();

        // A text node can either be a comment or a real text node.
        if (textNode.isComment())
            result = document.createComment(textValue);
        else
            result = document.createTextNode(textValue);
    }
    else
    {
    // Not sure what to do here.
    }

    return (result);
    }

    /**
    * Appends a SOAP Node to a DOM Node, by creating DOM Node objects to
    * represent the same information in the SOAP Node. The Document object is
    * needed as a factory to create DOM Node objects.
    */
    private void appendSOAPNodeToDOMNode(Document document, org.w3c.dom.Node domNode,javax.xml.soap.Node soapNode)
    throws ParserConfigurationException
    {
    org.w3c.dom.Node newDOMNode = createDOMNodeFromSOAPNode(soapNode, document);

    // Now that the new element is completely constructed (including its
    // children), add it to the parent element.

    domNode.appendChild(newDOMNode);
    }
    
    
    private static void dump(Source source) throws Exception
    {
         TransformerFactory  tf      = TransformerFactory.newInstance();
         Transformer         t       = tf.newTransformer();
         StreamResult        stdOut  = new StreamResult(System.out);

         System.out.println("[BEGIN_MESSAGE_DUMP]");
         t.transform(source, stdOut);
         System.out.println("[END_MESSAGE_DUMP]");
    }
    
    
    /**
     * Extracts request/response payload from the soap body.
     *
     * @param soapBody soap body message.
     * @param operation operation requested.
     * @param isFault boolean indicating if it is a fault.
     *
     * @return request payload
     *
     * @throws JBIException - if request could not be extracted.
     */
    protected Node extractPayload(SOAPBody soapBody, Operation operation, boolean isFault)
        throws JBIException
    {
	mLogger.fine( mStringTranslator.getString("SBC_EXTRACT_REQUEST_PAYLOAD") );

        return getChildElement(soapBody);
    }

    /**
     * Used to check if the response code corresponds to a fault.
     *
     * @param responseCode response code
     *
     * @return true if it is a fault; false otherwise.
     */
    public boolean isFault(int responseCode)
    {
        return false;
    }

    /**
     * Extracts the first Element node from the parent node.
     *
     * @param parentNode parent node
     *
     * @return first child element node.
     */
    private Node getChildElement(Node parentNode)
    {
        NodeList childNodes = parentNode.getChildNodes();
        Node currentNode = null;
        Node elementNode = null;

        for (int i = 0; i < childNodes.getLength(); i++)
        {
            currentNode = childNodes.item(i);

            if (currentNode.getNodeType() == Node.ELEMENT_NODE)
            {
                elementNode = currentNode;

                break;
            }
        }

        return elementNode;
    }

    /**
     * Extracts the fault code from the String.
     *
     * @param completeFaultCode fault code containing the namespace prefix and the code.
     *
     * @return the fault code without the namespace prefix
     */
    private String extractFaultCode(String completeFaultCode)
    {
        String faultCode;
        StringTokenizer tokenizer = new StringTokenizer(completeFaultCode,
                                                        ":");
        if ( tokenizer.countTokens() == 1)
        {
            faultCode = completeFaultCode;
        }
        else
        {
            // Discard the first token which is hte namespace prefix.
            tokenizer.nextToken();
            faultCode = tokenizer.nextToken();
        }
        return faultCode;
    }

    /**
     * Normalizes the attachments sent as part of the SoapMessage.
     *
     * @param soapMessage soap Message
     * @param normalizedMessage normalized Message
     *
     * @throws SOAPException if soap message cannot be read
     * @throws MessagingException if attachments cannot be added to normalized message.
     */
    private void normalizeAttachments(SOAPMessage soapMessage,
                                      NormalizedMessage normalizedMessage)
        throws SOAPException, MessagingException
    {
        if ( soapMessage != null)
        {
            if ( soapMessage.countAttachments() > 0  )
            {
                Iterator attachmentIter = soapMessage.getAttachments();
                for (; attachmentIter.hasNext();)
                {
                    AttachmentPart attachment = (AttachmentPart) attachmentIter.next();
                    DataHandler dataHandler = attachment.getDataHandler();
                    String contentId = attachment.getContentId();
                    normalizedMessage.addAttachment( contentId, dataHandler);
                }
            }
        }
    }
}
