/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This 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 (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.ws.core.soap;

//$Id: EnvelopeBuilderDOM.java 13528 2010-12-24 06:37:23Z jim.ma $

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;

import javax.xml.namespace.QName;
import javax.xml.soap.Name;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPBodyElement;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import javax.xml.transform.dom.DOMSource;

import org.jboss.logging.Logger;
import org.jboss.ws.Constants;
import org.jboss.ws.WSException;
import org.jboss.ws.core.CommonSOAPFaultException;
import org.jboss.wsf.common.DOMUtils;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

/**
 * A SOAPEnvelope builder for JAXRPC based on DOM 
 * 
 * @author Heiko Braun, <heiko.braun@jboss.com>
 * @author Thomas.Diesler@jboss.com
 * @since 19-Apr-2006
 */
public class EnvelopeBuilderDOM implements EnvelopeBuilder
{
   // provide logging
   private static Logger log = Logger.getLogger(EnvelopeBuilderDOM.class);

   private SOAPFactoryImpl soapFactory = new SOAPFactoryImpl();
   private Style style;

   public Style getStyle()
   {
      return style;
   }

   public void setStyle(Style style)
   {
      this.style = style;
   }

   public SOAPEnvelope build(SOAPMessage soapMessage, InputStream ins, boolean ignoreParseError) throws IOException, SOAPException
   {
      // Parse the XML input stream
      Element domEnv = null;
      try
      {
         domEnv = DOMUtils.parse(ins);
      }
      catch (IOException ex)
      {
         if (ignoreParseError)
         {
            return null;
         }
         QName faultCode = Constants.SOAP11_FAULT_CODE_CLIENT;
         throw new CommonSOAPFaultException(faultCode, ex.getMessage());
      }

      return build(soapMessage, domEnv);
   }

   public SOAPEnvelope build(SOAPMessage soapMessage, Reader reader, boolean ignoreParseError) throws IOException, SOAPException
   {
      // Parse the XML input stream
      Element domEnv = null;
      try
      {
         domEnv = DOMUtils.parse(new InputSource(reader));
      }
      catch (IOException ex)
      {
         if (ignoreParseError)
         {
            return null;
         }
         QName faultCode = Constants.SOAP11_FAULT_CODE_CLIENT;
         throw new CommonSOAPFaultException(faultCode, ex.getMessage());
      }

      return build(soapMessage, domEnv);
   }

   public SOAPEnvelope build(SOAPMessage soapMessage, Element domEnv) throws SOAPException
   {
      // Construct the envelope
      SOAPPartImpl soapPart = (SOAPPartImpl)soapMessage.getSOAPPart();
      SOAPEnvelopeImpl soapEnv = new SOAPEnvelopeImpl(soapPart, soapFactory.createElement(domEnv, false), false);

      DOMUtils.copyAttributes(soapEnv, domEnv);

      NodeList envChildNodes = domEnv.getChildNodes();
      for (int i = 0; i < envChildNodes.getLength(); i++)
      {
         Node child = envChildNodes.item(i);
         short childType = child.getNodeType();
         if (childType == Node.ELEMENT_NODE)
         {
            String elName = child.getLocalName();
            if ("Header".equals(elName))
            {
               buildSOAPHeader(soapEnv, (Element)child);
            }
            else if ("Body".equals(elName))
            {
               buildSOAPBody(soapEnv, (Element)child);
            }
            else
            {
               log.warn("Ignore envelope child: " + elName);
            }
         }
      }

      return soapEnv;
   }

   private SOAPHeader buildSOAPHeader(SOAPEnvelopeImpl soapEnv, Element domHeader) throws SOAPException
   {
      SOAPHeader soapHeader = soapEnv.getHeader();
      if (soapHeader == null)
         soapHeader = soapEnv.addHeader();

      DOMUtils.copyAttributes(soapHeader, domHeader);
      
      if (!soapHeader.getPrefix().equals(domHeader.getPrefix()))
      {
         soapHeader.setPrefix(domHeader.getPrefix());
      }
    	       
      NodeList headerChildNodes = domHeader.getChildNodes();
      for (int i = 0; i < headerChildNodes.getLength(); i++)
      {
         Node child = headerChildNodes.item(i);
         short childType = child.getNodeType();
         if (childType == Node.ELEMENT_NODE)
         {
            Element srcElement = (Element)child;
            XMLFragment xmlFragment = new XMLFragment(new DOMSource(srcElement));

            Name name = new NameImpl(srcElement.getLocalName(), srcElement.getPrefix(), srcElement.getNamespaceURI());
            SOAPContentElement destElement = new SOAPHeaderElementImpl(name);
            soapHeader.addChildElement(destElement);

            DOMUtils.copyAttributes(destElement, srcElement);
            destElement.setXMLFragment(xmlFragment);
         }
         else
         {
            log.warn("Ignore child type: " + childType);
         }
      }

      return soapHeader;
   }

   private SOAPBody buildSOAPBody(SOAPEnvelopeImpl soapEnv, Element domBody) throws SOAPException
   {
      String envNS = soapEnv.getNamespaceURI();

      SOAPBodyImpl soapBody = (SOAPBodyImpl)soapEnv.getBody();
      if (soapBody == null)
         soapBody = (SOAPBodyImpl)soapEnv.addBody();

      DOMUtils.copyAttributes(soapBody, domBody);
      
      if (!soapBody.getPrefix().equals(domBody.getPrefix()))
      {
         soapBody.setPrefix(domBody.getPrefix());
      }

      SOAPBodyElement soapBodyElement = null;
      boolean attachHRefElements = Constants.URI_SOAP11_ENC.equals(soapEnv.getAttributeNS(envNS, "encodingStyle"));

      NodeList bodyChildNodes = domBody.getChildNodes();
      for (int i = 0; i < bodyChildNodes.getLength(); i++)
      {
         Node child = bodyChildNodes.item(i);
         short childType = child.getNodeType();
         if (childType == Node.ELEMENT_NODE)
         {
            if (soapBodyElement == null)
            {
               soapBodyElement = buildSOAPBodyElement(soapEnv, (Element)child);
               attachHRefElements = attachHRefElements || Constants.URI_SOAP11_ENC.equals(soapBody.getAttributeNS(envNS, "encodingStyle"));
            }
            else if (attachHRefElements)
            {
               // Process additional soap encoded body elements
               soapBody.addChildElement(soapFactory.createElement((Element)child, true));
            }
         }
         else if (childType == Node.COMMENT_NODE)
         {
            appendCommentNode(soapBody, child);
         }
         else if (childType == Node.TEXT_NODE)
         {
            appendTextNode(soapBody, child);
         }
         else
         {
            log.warn("Ignore child type: " + childType);
         }
      }

      // Inline all attached href elements
      if (attachHRefElements)
      {
         HRefInlineHandler inlineHandler = new HRefInlineHandler(soapBody);
         inlineHandler.processHRefs();
      }

      return soapBody;
   }

   private SOAPBodyElement buildSOAPBodyElement(SOAPEnvelopeImpl soapEnv, Element domBodyElement) throws SOAPException
   {
      String envNS = soapEnv.getNamespaceURI();
      String envPrefix = soapEnv.getPrefix();

      SOAPBodyImpl soapBody = (SOAPBodyImpl)soapEnv.getBody();
      QName beName = DOMUtils.getElementQName(domBodyElement);

      SOAPBodyElement soapBodyElement = null;

      // Process a <env:Fault> message
      if (beName.equals(new QName(envNS, "Fault")))
      {
         SOAPFaultImpl soapFault = new SOAPFaultImpl(envPrefix, envNS);
         soapBody.addChildElement(soapFault);
         soapBodyElement = soapFault;

         DOMUtils.copyAttributes(soapFault, domBodyElement);

         // copy everything and let soapFault discover child elements itself
         XMLFragment xmlFragment = new XMLFragment(new DOMSource(domBodyElement));
         soapFault.setXMLFragment(xmlFragment);
      }

      // Process and RPC or DOCUMENT style message
      else
      {
         if (style == Style.DOCUMENT)
         {
            buildBodyElementDoc(soapBody, domBodyElement);
         }
         else if (style == Style.RPC)
         {
            soapBodyElement = buildBodyElementRpc(soapBody, domBodyElement);
         }
         else if (style == null)
         {
            buildBodyElementDefault(soapBody, domBodyElement);
         }
         else
         {
            throw new WSException("Unsupported message style: " + style);
         }
      }

      return soapBodyElement;
   }

   public SOAPBodyElement buildBodyElementDoc(SOAPBodyImpl soapBody, Element domBodyElement) throws SOAPException
   {
      Element srcElement = (Element)domBodyElement;

      QName beName = DOMUtils.getElementQName(domBodyElement);
      SOAPBodyElementDoc soapBodyElement = new SOAPBodyElementDoc(beName);
      SOAPContentElement contentElement = (SOAPContentElement)soapBody.addChildElement(soapBodyElement);

      DOMUtils.copyAttributes(contentElement, srcElement);

      XMLFragment xmlFragment = new XMLFragment(new DOMSource(srcElement));
      contentElement.setXMLFragment(xmlFragment);

      return soapBodyElement;
   }

   public SOAPBodyElement buildBodyElementRpc(SOAPBodyImpl soapBody, Element domBodyElement) throws SOAPException
   {
      QName beName = DOMUtils.getElementQName(domBodyElement);
      SOAPBodyElementRpc soapBodyElement = new SOAPBodyElementRpc(beName);
      soapBodyElement = (SOAPBodyElementRpc)soapBody.addChildElement(soapBodyElement);

      DOMUtils.copyAttributes(soapBodyElement, domBodyElement);

      NodeList nlist = domBodyElement.getChildNodes();
      for (int i = 0; i < nlist.getLength(); i++)
      {
         Node child = nlist.item(i);
         short childType = child.getNodeType();
         if (childType == Node.ELEMENT_NODE)
         {
            Element srcElement = (Element)child;
            Name name = new NameImpl(srcElement.getLocalName(), srcElement.getPrefix(), srcElement.getNamespaceURI());
            SOAPContentElement destElement = new SOAPContentElement(name);
            destElement = (SOAPContentElement)soapBodyElement.addChildElement(destElement);

            DOMUtils.copyAttributes(destElement, srcElement);

            XMLFragment xmlFragment = new XMLFragment(new DOMSource(srcElement));
            destElement.setXMLFragment(xmlFragment);
         }
         else if (childType == Node.COMMENT_NODE)
         {
            appendCommentNode(soapBodyElement, child);
         }
         else if (childType == Node.TEXT_NODE)
         {
            appendTextNode(soapBodyElement, child);
         }
         else
         {
            log.warn("Ignore child type: " + childType);
         }
      }

      return soapBodyElement;
   }

   public SOAPBodyElement buildBodyElementDefault(SOAPBodyImpl soapBody, Element domBodyElement) throws SOAPException
   {
      QName beName = DOMUtils.getElementQName(domBodyElement);
      SOAPBodyElement soapBodyElement = new SOAPBodyElementMessage(beName);
      soapBodyElement = (SOAPBodyElementMessage)soapBody.addChildElement(soapBodyElement);

      DOMUtils.copyAttributes(soapBodyElement, domBodyElement);

      NodeList nlist = domBodyElement.getChildNodes();
      for (int i = 0; i < nlist.getLength(); i++)
      {
         Node child = nlist.item(i);
         short childType = child.getNodeType();
         if (childType == Node.ELEMENT_NODE)
         {
            SOAPElement soapElement = soapFactory.createElement((Element)child);
            soapBodyElement.addChildElement(soapElement);
         }
         else if (childType == Node.COMMENT_NODE)
         {
            appendCommentNode(soapBodyElement, child);
         }
         else if (childType == Node.TEXT_NODE)
         {
            appendTextNode(soapBodyElement, child);
         }
         else if (childType == Node.CDATA_SECTION_NODE)
         {
            String nodeValue = child.getNodeValue();
            soapBodyElement.addTextNode(nodeValue);
         }
         else
         {
            log.warn("Ignore child type: " + childType);
         }
      }

      return soapBodyElement;
   }

   private void appendCommentNode(SOAPElement soapElement, Node child)
   {
      String nodeValue = child.getNodeValue();
      Document ownerDoc = soapElement.getOwnerDocument();
      Comment comment = ownerDoc.createComment(nodeValue);
      soapElement.appendChild(comment);
   }

   private void appendTextNode(SOAPElement soapElement, Node child) throws SOAPException
   {
      String nodeValue = child.getNodeValue();
      soapElement.addTextNode(nodeValue);
   }
}
