/*
 * The Apache Software License, Version 1.1
 *
 *
 * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Axis" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
package org.apache.axis.message;

import org.apache.axis.AxisFault;
import org.apache.axis.Constants;
import org.apache.axis.Message;
import org.apache.axis.MessageContext;
import org.apache.axis.client.AxisClient;
import org.apache.axis.components.logger.LogFactory;
import org.apache.axis.configuration.NullProvider;
import org.apache.axis.encoding.DeserializationContext;
import org.apache.axis.encoding.DeserializationContextImpl;
import org.apache.axis.encoding.SerializationContext;
import org.apache.axis.schema.SchemaVersion;
import org.apache.axis.soap.SOAPConstants;
import org.apache.axis.utils.Mapping;
import org.apache.axis.utils.Messages;
import org.apache.commons.logging.Log;
import org.w3c.dom.DOMException;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.namespace.QName;
import javax.xml.soap.SOAPException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Vector;

/**
 * Implementation of a SOAP Envelope
 */
public class SOAPEnvelopeAxisImpl extends SOAPEnvelopeImpl
{

   private static Log log = LogFactory.getLog(SOAPEnvelopeAxisImpl.class.getName());

   private SOAPHeaderAxisImpl header;
   private SOAPBodyAxisImpl body;

   public Vector trailers = new Vector();
   private SOAPConstants soapConstants;
   private SchemaVersion schemaVersion = SchemaVersion.SCHEMA_2001;

   // This is a hint to any service description to tell it what
   // "type" of message we are.  This might be "request", "response",
   // or anything else your particular service descripton requires.
   //
   // This gets passed back into the service description during
   // deserialization
   public String messageType;

   // Set to mark the envelope as modified
   private boolean modified;

   // The rpc invoker can set this flag during processing, this will
   //    - turn on SOAPElement immutable protection
   //    - turn off MessagePart as string caching
   private boolean processingRPCInvocation;

   public SOAPEnvelopeAxisImpl()
   {
      this(true, SOAPConstants.SOAP11_CONSTANTS);
   }

   public SOAPEnvelopeAxisImpl(SOAPConstants soapConstants)
   {
      this(true, soapConstants);
   }

   public SOAPEnvelopeAxisImpl(SOAPConstants soapConstants,
                               SchemaVersion schemaVersion)
   {
      this(true, soapConstants, schemaVersion);
   }

   public SOAPEnvelopeAxisImpl(boolean registerPrefixes, SOAPConstants soapConstants)
   {
      this(registerPrefixes, soapConstants, SchemaVersion.SCHEMA_2001);
   }

   public SOAPEnvelopeAxisImpl(boolean registerPrefixes, SOAPConstants soapConstants, SchemaVersion schemaVersion)
   {

      // FIX BUG http://nagoya.apache.org/bugzilla/show_bug.cgi?id=18108
      super(Constants.ELEM_ENVELOPE, Constants.NS_PREFIX_SOAP_ENV,
              (soapConstants != null) ? soapConstants.getEnvelopeURI() : Constants.DEFAULT_SOAP_VERSION.getEnvelopeURI());

      if (soapConstants == null)
         soapConstants = Constants.DEFAULT_SOAP_VERSION;

      // FIX BUG http://nagoya.apache.org/bugzilla/show_bug.cgi?id=18108

      this.soapConstants = soapConstants;
      this.schemaVersion = schemaVersion;

      setHeader(new SOAPHeaderAxisImpl(this, soapConstants));
      setBody(new SOAPBodyAxisImpl(this, soapConstants));

      if (registerPrefixes)
      {
         if (namespaces == null)
            namespaces = new ArrayList();

         namespaces.add(new Mapping(soapConstants.getEnvelopeURI(),
                 Constants.NS_PREFIX_SOAP_ENV));
         namespaces.add(new Mapping(schemaVersion.getXsdURI(),
                 Constants.NS_PREFIX_SCHEMA_XSD));
         namespaces.add(new Mapping(schemaVersion.getXsiURI(),
                 Constants.NS_PREFIX_SCHEMA_XSI));
      }

      setDirty(true);
   }

   public static SOAPEnvelopeAxisImpl newSOAPEnvelope(InputStream input) throws SAXException
   {

      SOAPEnvelopeAxisImpl env = new SOAPEnvelopeAxisImpl();

      InputSource is = new InputSource(input);

      // FIX BUG http://nagoya.apache.org/bugzilla/show_bug.cgi?id=18108
      AxisClient tmpEngine = new AxisClient(new NullProvider());
      MessageContext msgContext = new MessageContext(tmpEngine);
      DeserializationContext dser = new DeserializationContextImpl(is, msgContext, Message.REQUEST, env);
      dser.parse();

      return env;
   }

   /**
    * Get the Message Type (REQUEST/RESPONSE)
    *
    * @return message type
    */
   public String getMessageType()
   {
      return messageType;
   }

   /**
    * Set the Message Type (REQUEST/RESPONSE)
    *
    * @param messageType
    */
   public void setMessageType(String messageType)
   {
      this.messageType = messageType;
   }

   /**
    * Get all the BodyElement's in the soap body
    *
    * @return vector with body elements
    * @throws AxisFault
    */
   public Vector getBodyElements() throws AxisFault
   {
      if (body != null)
      {
         return new Vector(body.getBodyElements());
      }
      else
      {
         return new Vector();
      }
   }

   /**
    * Return trailers
    *
    * @return
    */
   public Vector getTrailers()
   {
      return trailers;
   }

   /**
    * Get the first BodyElement in the SOAP Body
    *
    * @return first Body Element
    * @throws AxisFault
    */
   public SOAPBodyElementAxisImpl getFirstBody() throws AxisFault
   {
      return (body != null ? body.getFirstBody() : null);
   }

   public boolean isModified()
   {
      return modified;
   }

   public void setModified(boolean modified)
   {
      this.modified = modified;
      log.debug("setModifiedAfterSerialization: " + modified);
   }

   public boolean isProcessingRPCInvocation()
   {
      return processingRPCInvocation;
   }

   public void setProcessingRPCInvocation(boolean flag)
   {
      log.debug("setProcessingRPCInvocation: " + flag);
      this.processingRPCInvocation = flag;
   }

   /**
    * Get Headers
    *
    * @return Vector containing Header's
    * @throws AxisFault
    */
   public Vector getHeaders() throws AxisFault
   {
      if (header != null)
      {
         return new Vector(header.getChildren());
      }
      else
      {
         return new Vector();
      }
   }

   /**
    * Add a HeaderElement
    *
    * @param hdr
    */
   public void addHeader(SOAPHeaderElementAxisImpl hdr)
   {
      if (header == null)
      {
         header = new SOAPHeaderAxisImpl(this, soapConstants);
      }
      hdr.setEnvelope(this);
      header.addHeader(hdr);
      setDirty(true);
   }

   /**
    * Add a SOAP Body Element
    *
    * @param element
    */
   public void addBodyElement(SOAPBodyElementAxisImpl element)
   {
      element.setEnvelope(this);
      body.addBodyElement(element);
      setDirty(true);
   }

   /**
    * Remove all headers
    */
   public void removeHeaders()
   {
      header = null;
   }

   /**
    * Set the SOAP Header
    */
   public void setHeader(SOAPHeaderAxisImpl header)
   {
      try
      {
         if (this.header != null)
            removeChild(this.header);

         // cast to force exception if wrong type
         header.setParentElement(this);
         this.header = header;
      }
      catch (SOAPException ex)
      {
         // class cast should never fail when parent is a SOAPEnvelope
         log.fatal(Messages.getMessage("exception00"), ex);
      }
   }

   /**
    * Remove a Header Element from SOAP Header
    *
    * @param hdr
    */
   public void removeHeader(SOAPHeaderElementAxisImpl hdr)
   {
      if (header != null)
      {
         header.removeHeader(hdr);
         setDirty(true);
      }
   }

   public Node removeChild(Node oldChild) throws DOMException
   {
      super.removeChild(oldChild);

      if (oldChild instanceof SOAPHeaderAxisImpl)
         removeHeaders();

      else if (oldChild instanceof SOAPBodyAxisImpl)
         removeBody();

      return oldChild;
   }

   /**
    * Remove the SOAP Body
    */
   public void removeBody()
   {
      body = null;
   }

   /**
    * Set the soap body
    *
    * @param newBody
    */
   public void setBody(SOAPBodyAxisImpl newBody)
   {

      if (body != null)
      {
         removeChild(body);
      }

      try
      {
         newBody.setParentElement(this);
         body = newBody;
      }
      catch (SOAPException ex)
      {
         // class cast should never fail when parent is a SOAPEnvelope
         log.fatal(Messages.getMessage("exception00"), ex);
      }
   }

   /**
    * Remove a Body Element from the soap body
    *
    * @param element
    */
   public void removeBodyElement(SOAPBodyElementAxisImpl element)
   {
      if (body != null)
      {
         body.removeBodyElement(element);
         setDirty(true);
      }
   }

   /**
    * Remove an element from the trailer
    *
    * @param element
    */
   public void removeTrailer(SOAPElementAxisImpl element)
   {
      if (log.isDebugEnabled())
         log.debug(Messages.getMessage("removeTrailer00"));
      trailers.removeElement(element);
      setDirty(true);
   }

   /**
    * clear the elements in the soap body
    */
   public void clearBody()
   {
      if (body != null)
      {
         body.clearBody();
         setDirty(true);
      }
   }

   /**
    * Add an element to the trailer
    *
    * @param element
    */
   public void addTrailer(SOAPElementAxisImpl element)
   {
      if (log.isDebugEnabled())
         log.debug(Messages.getMessage("removeTrailer00"));
      element.setEnvelope(this);
      trailers.addElement(element);
      setDirty(true);
   }

   /**
    * Get a header by name (always respecting the currently in-scope
    * actors list)
    */
   public SOAPHeaderElementAxisImpl getHeaderByName(String namespace,
                                                    String localPart)
           throws AxisFault
   {
      return getHeaderByName(namespace, localPart, false);
   }

   /**
    * Get a header by name, filtering for headers targeted at this
    * engine depending on the accessAllHeaders parameter.
    */
   public SOAPHeaderElementAxisImpl getHeaderByName(String namespace,
                                                    String localPart,
                                                    boolean accessAllHeaders)
           throws AxisFault
   {
      if (header != null)
      {
         return header.getHeaderByName(namespace,
                 localPart,
                 accessAllHeaders);
      }
      else
      {
         return null;
      }
   }

   /**
    * Get a body element given its name
    *
    * @param namespace
    * @param localPart
    * @return
    * @throws AxisFault
    */
   public SOAPBodyElementAxisImpl getBodyByName(String namespace, String localPart) throws AxisFault
   {
      return (body != null ? body.getBodyByName(namespace, localPart) : null);
   }

   /**
    * Get an enumeration of header elements given the namespace and localpart
    *
    * @param namespace
    * @param localPart
    * @return
    * @throws AxisFault
    */
   public Enumeration getHeadersByName(String namespace, String localPart)
           throws AxisFault
   {
      return getHeadersByName(namespace, localPart, false);
   }

   /**
    * Return an Enumeration of headers which match the given namespace
    * and localPart.  Depending on the value of the accessAllHeaders
    * parameter, we will attempt to filter on the current engine's list
    * of actors.
    * <p/>
    * !!! NOTE THAT RIGHT NOW WE ALWAYS ASSUME WE'RE THE "ULTIMATE
    * DESTINATION" (i.e. we match on null actor).  IF WE WANT TO FULLY SUPPORT
    * INTERMEDIARIES WE'LL NEED TO FIX THIS.
    */
   public Enumeration getHeadersByName(String namespace, String localPart,
                                       boolean accessAllHeaders)
           throws AxisFault
   {
      if (header != null)
      {
         return header.getHeadersByName(namespace,
                 localPart,
                 accessAllHeaders);
      }
      else
      {
         return new Vector().elements();
      }
   }

   /**
    * Should make SOAPSerializationException?
    */
   public void outputImpl(SerializationContext context) throws Exception
   {

      boolean oldPretty = context.getPretty();
      context.setPretty(true);

      // Register namespace prefixes.
      if (namespaces != null)
      {
         for (Iterator i = namespaces.iterator(); i.hasNext();)
         {
            Mapping mapping = (Mapping)i.next();
            context.registerPrefixForURI(mapping.getPrefix(),
                    mapping.getNamespaceURI());
         }
      }

      Enumeration enum;

      // Output <SOAP-ENV:Envelope>
      context.startElement(new QName(soapConstants.getEnvelopeURI(),
              Constants.ELEM_ENVELOPE), attributes);

      // Output non-SOAPHeader and non-SOAPBody stuff.
      for (Iterator it = getChildElements(); it.hasNext();)
      {
         Node childNode = (Node)it.next();
         if (childNode instanceof SOAPHeaderAxisImpl || childNode instanceof SOAPBodyAxisImpl)
            continue;

         if (childNode instanceof SOAPElementAxisImpl)
            ((SOAPElementAxisImpl)childNode).output(context);
         else if (childNode instanceof TextImpl)
            context.writeString(childNode.getNodeValue());
      }

      // Output headers
      if (header != null)
      {
         header.outputImpl(context);
      }

      // Output body
      if (body != null)
      {
         body.outputImpl(context);
      }

      // Output trailers
      enum = trailers.elements();
      while (enum.hasMoreElements())
      {
         SOAPElementAxisImpl element = (SOAPElementAxisImpl)enum.nextElement();
         element.output(context);
         // Output this independent element
      }

      // Output </SOAP-ENV:Envelope>
      context.endElement();

      context.setPretty(oldPretty);

      setModified(false);
   }

   /**
    * Get the soap constants for this envelope
    *
    * @return
    */
   public SOAPConstants getSOAPConstants()
   {
      return soapConstants;
   }

   /**
    * Set the soap constants for this envelope
    *
    * @param soapConstants
    */
   public void setSoapConstants(SOAPConstants soapConstants)
   {
      this.soapConstants = soapConstants;
   }

   /**
    * Get the schema version for this envelope
    *
    * @return
    */
   public SchemaVersion getSchemaVersion()
   {
      return schemaVersion;
   }

   /**
    * Set the schema version for this envelope
    *
    * @param schemaVersion
    */
   public void setSchemaVersion(SchemaVersion schemaVersion)
   {
      this.schemaVersion = schemaVersion;
   }

   /**
    * Add a soap body if one does not exist
    *
    * @return
    * @throws SOAPException
    */
   public javax.xml.soap.SOAPBody addBody() throws SOAPException
   {
      if (body == null)
      {
         setBody(new SOAPBodyAxisImpl(this, soapConstants));
         return body;
      }
      else
      {
         throw new SOAPException(Messages.getMessage("bodyPresent"));
      }
   }

   /**
    * Add a soap header if one does not exist
    *
    * @return
    * @throws SOAPException
    */
   public javax.xml.soap.SOAPHeader addHeader() throws SOAPException
   {
      if (header == null)
      {
         header = new SOAPHeaderAxisImpl(this, soapConstants);
         return header;
      }
      else
      {
         throw new SOAPException(Messages.getMessage("headerPresent"));
      }
   }

   /**
    * create a Name given the local part
    *
    * @param localName
    * @return
    * @throws SOAPException
    */
   public javax.xml.soap.Name createName(String localName)
           throws SOAPException
   {
      return new PrefixedQName(null, localName, null);
   }

   /**
    * Create a name given local part, prefix and uri
    *
    * @param localName
    * @param prefix
    * @param uri
    * @return
    * @throws SOAPException
    */
   public javax.xml.soap.Name createName(String localName,
                                         String prefix,
                                         String uri)
           throws SOAPException
   {
      return new PrefixedQName(uri, localName, prefix);
   }

   /**
    * Get the soap body
    */
   public javax.xml.soap.SOAPBody getBody()
   {
      return body;
   }

   /**
    * Get the soap header
    *
    * @return
    */
   public javax.xml.soap.SOAPHeader getHeader()
   {
      return header;
   }

   public void setSAAJEncodingCompliance(boolean comply)
   {
      this.body.setSAAJEncodingCompliance(comply);
   }
}
