/*
 * 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: SOAPBodyImpl.java 13435 2010-12-08 20:34:10Z darran.lofthouse@jboss.com $

import java.util.Iterator;
import java.util.Locale;

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.SOAPException;
import javax.xml.soap.SOAPFault;
import javax.xml.soap.Text;

import org.jboss.logging.Logger;
import org.jboss.ws.Constants;
import org.jboss.wsf.common.DOMUtils;
import org.w3c.dom.Comment;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
 * An object that represents the contents of the SOAP body element in a SOAP message.
 * A SOAP body element consists of XML data that affects the way the application-specific content is processed.
 *
 * A SOAPBody object contains SOAPBodyElement objects, which have the content for the SOAP body.
 * A SOAPFault object, which carries status and/or error information, is an example of a SOAPBodyElement object.
 *
 * @author Thomas.Diesler@jboss.org
 */
public class SOAPBodyImpl extends SOAPElementImpl implements SOAPBody
{
   // provide logging
   private static Logger log = Logger.getLogger(SOAPBodyImpl.class);

   public SOAPBodyImpl(String prefix, String namespace)
   {
      super("Body", prefix, namespace);
   }

   /** Convert the child into a SOAPBodyElement */
   public SOAPElement addChildElement(SOAPElement child) throws SOAPException
   {
      log.trace("addChildElement: " + child.getElementName());

      if ((child instanceof SOAPBodyElement) == false)
         child = convertToBodyElement(child);

      child = super.addChildElement(child);
      return child;
   }

   public SOAPBodyElement addBodyElement(Name name) throws SOAPException
   {
      log.trace("addBodyElement: " + name);
      SOAPBodyElement child = new SOAPBodyElementDoc(name);
      return (SOAPBodyElement)addChildElement(child);
   }

   public SOAPBodyElement addBodyElement(QName qname) throws SOAPException
   {
      log.trace("addBodyElement: " + qname);
      SOAPBodyElement child = new SOAPBodyElementDoc(qname);
      return (SOAPBodyElement)addChildElement(child);
   }

   public SOAPBodyElement addDocument(Document doc) throws SOAPException
   {
      log.trace("addDocument");
      Element rootElement = doc.getDocumentElement();
      SOAPFactoryImpl soapFactory = new SOAPFactoryImpl();
      SOAPElement soapElement = soapFactory.createElement(rootElement);
      return (SOAPBodyElement)addChildElement(soapElement);
   }

   public SOAPFault addFault() throws SOAPException
   {
      log.trace("addFault");
      if (hasFault())
         throw new SOAPException("A SOAPBody may contain at most one SOAPFault child element");

      SOAPFaultImpl soapFault = new SOAPFaultImpl(getPrefix(), getNamespaceURI());
      soapFault = (SOAPFaultImpl)addChildElement(soapFault);
      soapFault.setFaultCode(soapFault.getDefaultFaultCode());
      return soapFault;
   }

   public SOAPFault addFault(Name faultCode, String faultString) throws SOAPException
   {
      log.trace("addFault");
      if (hasFault())
         throw new SOAPException("A SOAPBody may contain at most one SOAPFault child element");

      SOAPFaultImpl soapFault = new SOAPFaultImpl(getPrefix(), getNamespaceURI());
      soapFault = (SOAPFaultImpl)addChildElement(soapFault);
      soapFault.setFaultCode(faultCode);
      soapFault.setFaultString(faultString);
      return soapFault;
   }

   public SOAPFault addFault(QName faultCode, String faultString) throws SOAPException
   {
      log.trace("addFault");
      if (hasFault())
         throw new SOAPException("A SOAPBody may contain at most one SOAPFault child element");

      SOAPFaultImpl soapFault = new SOAPFaultImpl(getPrefix(), getNamespaceURI());
      soapFault = (SOAPFaultImpl)addChildElement(soapFault);
      soapFault.setFaultCode(faultCode);
      soapFault.setFaultString(faultString);
      return soapFault;
   }

   public SOAPFault addFault(Name faultCode, String faultString, Locale locale) throws SOAPException
   {
      log.trace("addFault");
      if (hasFault())
         throw new SOAPException("A SOAPBody may contain at most one SOAPFault child element");

      SOAPFaultImpl soapFault = new SOAPFaultImpl(getPrefix(), getNamespaceURI());
      soapFault.setFaultCode(faultCode);
      soapFault.setFaultString(faultString, locale);
      addChildElement(soapFault);
      return soapFault;
   }

   public SOAPFault addFault(QName faultCode, String faultString, Locale locale) throws SOAPException
   {
      log.trace("addFault");
      if (hasFault())
         throw new SOAPException("A SOAPBody may contain at most one SOAPFault child element");

      SOAPFaultImpl soapFault = new SOAPFaultImpl(getPrefix(), getNamespaceURI());
      soapFault.setFaultCode(faultCode);
      soapFault.setFaultString(faultString, locale);
      addChildElement(soapFault);
      return soapFault;
   }

   public SOAPFault getFault()
   {
      log.trace("getFault");
      Iterator it = faultIterator();
      SOAPFault soapFault = it.hasNext() ? (SOAPFault)it.next() : null;
      return soapFault;
   }

   public boolean hasFault()
   {
      log.trace("hasFault");
      return faultIterator().hasNext();
   }

   private Iterator faultIterator()
   {
      return getChildElements(new QName(getNamespaceURI(), "Fault"));
   }

   public SOAPBodyElement getBodyElement()
   {
      SOAPBodyElement bodyElement = null;
      Iterator it = getChildElements();
      while (bodyElement == null && it.hasNext())
      {
         Object next = it.next();
         if (next instanceof SOAPBodyElement)
            bodyElement = (SOAPBodyElement)next;
      }
      return bodyElement;
   }

   public Node appendChild(Node newChild) throws DOMException
   {
      log.trace("appendChild: " + newChild.getNodeName());
      if (needsConversionToBodyElement(newChild))
         newChild = convertToBodyElement(newChild);

      return super.appendChild(newChild);
   }

   public Node insertBefore(Node newChild, Node refChild) throws DOMException
   {
      log.trace("insertBefore: " + newChild.getNodeName());
      if (needsConversionToBodyElement(newChild))
         newChild = convertToBodyElement(newChild);

      return super.insertBefore(newChild, refChild);
   }

   public Node replaceChild(Node newChild, Node oldChild) throws DOMException
   {
      log.trace("replaceChild: " + newChild.getNodeName());
      if (needsConversionToBodyElement(newChild))
         newChild = convertToBodyElement(newChild);

      return super.replaceChild(newChild, oldChild);
   }

   @Override
   public SOAPElement addAttribute(Name name, String value) throws SOAPException
   {
      String envNamespace = getNamespaceURI();
      if (Constants.NS_SOAP12_ENV.equals(envNamespace) && name.equals(new NameImpl("encodingStyle", Constants.PREFIX_ENV, envNamespace)))
         throw new SOAPException("Cannot set encodingStyle on: " + getElementQName());

      return super.addAttribute(name, value);
   }

   public Document extractContentAsDocument() throws SOAPException
   {
      log.trace("extractContentAsDocument");

      Iterator childElements = getChildElements();

      SOAPElementImpl childElement = null;

      while (childElements.hasNext() == true)
      {
         Object current = childElements.next();
         if (current instanceof SOAPElementImpl)
         {
            childElement = (SOAPElementImpl)current;
            break;
         }
      }

      // zero child elements?
      if (childElement == null)
         throw new SOAPException("Cannot find SOAPBodyElement");

      // more than one child element?
      while (childElements.hasNext() == true)
      {
         Object current = childElements.next();
         if (current instanceof SOAPElementImpl)
            throw new SOAPException("Multiple SOAPBodyElement");
      }

      if (childElement instanceof SOAPContentElement)
      {
         // cause expansion to DOM
         SOAPContentElement contentElement = (SOAPContentElement)childElement;
         // TODO change visibility of SOAPContentElement.expandToDOM() to package? 
         contentElement.hasChildNodes();
      }

      // child SOAPElement is removed as part of this process
      childElement.detachNode();

      // child element's owner document might be shared with other elements;
      // we have to create a separate document for returning to our caller
      Document newDocument = DOMUtils.getDocumentBuilder().newDocument();
      Node adoptedElement = newDocument.adoptNode(childElement.domNode);
      newDocument.appendChild(adoptedElement);

      return newDocument;
   }

   private static boolean needsConversionToBodyElement(Node newChild)
   {
      // JBCTS-440 #addTextNodeTest1 appends a Text node to a SOAPBody
      boolean validChild = newChild instanceof SOAPBodyElement;
      validChild = validChild || newChild instanceof DocumentFragment;
      validChild = validChild || newChild instanceof Text;
      validChild = validChild || newChild instanceof Comment;
      return validChild == false;
   }

   private static SOAPBodyElementDoc convertToBodyElement(Node node) throws DOMException
   {
      if (!(node instanceof SOAPElementImpl) && (node instanceof Element))
      {
         try
         {
            SOAPFactoryImpl soapFactory = new SOAPFactoryImpl();
            node = (SOAPElementImpl)soapFactory.createElement((Element)node);
         }
         catch (SOAPException ex)
         {
            throw new DOMException(DOMException.INVALID_STATE_ERR, "Could not convert Element to a SOAPElement");
         }
      }
      
      if (!(node instanceof SOAPElementImpl))
         throw new IllegalArgumentException("SOAPElement expected");

      SOAPElementImpl element = (SOAPElementImpl)node;
      element.detachNode();
      return new SOAPBodyElementDoc(element);
   }

}
