/*
 * Copyright 2002-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jboss.axis.message;

import org.jboss.axis.AxisFault;
import org.jboss.axis.Constants;
import org.jboss.axis.MessageContext;
import org.jboss.axis.encoding.DeserializationContext;
import org.jboss.axis.encoding.SerializationContext;
import org.jboss.axis.handlers.soap.SOAPService;
import org.jboss.axis.soap.SOAPConstants;
import org.jboss.axis.utils.Messages;
import org.jboss.logging.Logger;
import org.xml.sax.Attributes;

import javax.xml.namespace.QName;
import javax.xml.soap.Name;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeaderElement;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Vector;


/**
 * Holder for header elements.
 *
 * @author Glyn Normington (glyn@apache.org)
 */
public class SOAPHeaderAxisImpl extends SOAPHeaderImpl
{

   private static Logger log = Logger.getLogger(SOAPHeaderAxisImpl.class.getName());

   private SOAPConstants soapConstants;

   SOAPHeaderAxisImpl(SOAPEnvelopeAxisImpl env, SOAPConstants soapConsts)
   {
      super(Constants.ELEM_HEADER, Constants.NS_PREFIX_SOAP_ENV,
              (soapConsts != null) ? soapConsts.getEnvelopeURI() : Constants.DEFAULT_SOAP_VERSION.getEnvelopeURI());
      soapConstants = (soapConsts != null) ? soapConsts : Constants.DEFAULT_SOAP_VERSION;
      try
      {
         setParentElement(env);
         setEnvelope(env);
      }
      catch (SOAPException ex)
      {
         // class cast should never fail when parent is a SOAPEnvelope
         log.fatal(Messages.getMessage("exception00"), ex);
      }
   }

   public SOAPHeaderAxisImpl(String namespace, String localPart, String prefix,
                             Attributes attributes, DeserializationContext context,
                             SOAPConstants soapConsts) throws AxisFault
   {
      super(namespace, localPart, prefix, attributes, context);
      soapConstants = (soapConsts != null) ? soapConsts : Constants.DEFAULT_SOAP_VERSION;
   }

   public void setParentElement(SOAPElement parent) throws SOAPException
   {

      if (parent == null)
         throw new IllegalArgumentException(Messages.getMessage("nullParent00"));

      try
      {
         SOAPEnvelopeAxisImpl env = (SOAPEnvelopeAxisImpl)parent;
         super.setParentElement(env);
         setEnvelope(env);
      }
      catch (Throwable t)
      {
         throw new SOAPException(t);
      }
   }

   public SOAPHeaderElement addHeaderElement(Name name)
           throws SOAPException
   {
      SOAPHeaderElementAxisImpl headerElement = new SOAPHeaderElementAxisImpl(name);
      SOAPEnvelopeAxisImpl envelope = getEnvelope();
      headerElement.setEnvelope(envelope);
      addHeader(headerElement);
      envelope.setDirty(true);
      return headerElement;
   }

   public Iterator examineHeaderElements(String actor)
   {
      ArrayList results = new ArrayList();
      getHeaderElements(results, actor);
      return results.iterator();
   }

   public Iterator extractHeaderElements(String actor)
   {
      ArrayList results = new ArrayList();
      getHeaderElements(results, actor);

      // Detach the header elements from the header
      Iterator iterator = results.iterator();
      while (iterator.hasNext())
      {
         removeChild((SOAPHeaderElementAxisImpl)iterator.next());
      }

      return results.iterator();
   }

   public Iterator examineMustUnderstandHeaderElements(String actor)
   {
      ArrayList results = new ArrayList();
      getHeaderElements(results, actor);

      Iterator it = results.iterator();
      while (it.hasNext())
      {
         SOAPHeaderElementAxisImpl she = (SOAPHeaderElementAxisImpl)it.next();
         if (she.getMustUnderstand() == false)
            it.remove();
      }

      return results.iterator();
   }

   public Iterator examineAllHeaderElements()
   {
      return getChildren().iterator();
   }

   public Iterator extractAllHeaderElements()
   {
      Vector result = new Vector(getChildren());
      removeChildren();
      return result.iterator();
   }

   private void getHeaderElements(ArrayList results, String actor)
   {
      String nextActor = SOAPConstants.SOAP11_CONSTANTS.getNextRoleURI();

      Iterator it = getChildren().iterator();
      while (it.hasNext())
      {
         SOAPHeaderElementAxisImpl she = (SOAPHeaderElementAxisImpl)it.next();
         String headerActor = she.getActor();

         if ((nextActor.equals(headerActor) && she.getMustUnderstand() == false)
                 || (actor != null && actor.equals(headerActor)))
         {
            results.add(she);
         }
      }
   }

   void addHeader(SOAPHeaderElementAxisImpl header)
   {
      if (log.isDebugEnabled())
         log.debug(Messages.getMessage("addHeader00"));
      try
      {
         header.setParentElement(this);
      }
      catch (SOAPException ex)
      {
         // class cast should never fail when parent is a SOAPHeader
         log.fatal(Messages.getMessage("exception00"), ex);
      }
   }

   void removeHeader(SOAPHeaderElementAxisImpl header)
   {
      if (log.isDebugEnabled())
         log.debug(Messages.getMessage("removeHeader00"));

      removeChild(header);
   }

   /**
    * Get a header by name, filtering for headers targeted at this
    * engine depending on the accessAllHeaders parameter.
    */
   SOAPHeaderElementAxisImpl getHeaderByName(String namespace,
                                             String localPart,
                                             boolean accessAllHeaders)
   {
      SOAPHeaderElementAxisImpl header = (SOAPHeaderElementAxisImpl)findElement(namespace, localPart);

      // If we're operating within an AxisEngine, respect its actor list
      // unless told otherwise
      if (!accessAllHeaders)
      {
         MessageContext mc = MessageContext.getCurrentContext();
         if (mc != null)
         {
            if (header != null)
            {
               String actor = header.getActor();

               // Always respect "next" role
               String nextActor =
                       getEnvelope().getSOAPConstants().getNextRoleURI();
               if (nextActor.equals(actor))
                  return header;

               SOAPService soapService = mc.getService();
               if (soapService != null)
               {
                  ArrayList actors = mc.getService().getActors();
                  if ((actor != null) &&
                          (actors == null || !actors.contains(actor)))
                  {
                     header = null;
                  }
               }
            }
         }
      }

      return header;
   }

   private SOAPElementAxisImpl findElement(String namespace, String localPart)
   {
      if (hasChildNodes() == false)
         return null;

      QName qname = new QName(namespace, localPart);
      Iterator it = getChildren().iterator();
      while (it.hasNext())
      {
         SOAPHeaderElementAxisImpl she = (SOAPHeaderElementAxisImpl)it.next();
         if (she.getQName().equals(qname))
            return she;
      }

      return null;
   }

   /**
    * 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.
    */
   Enumeration getHeadersByName(String namespace,
                                String localPart,
                                boolean accessAllHeaders)
   {
      ArrayList actors = null;
      boolean firstTime = false;

      /** This might be optimizable by creating a custom Enumeration
       * which moves through the headers list (parsing on demand, again),
       * returning only the next one each time.... this is Q&D for now.
       */
      Vector v = new Vector();
      String nextActor = getEnvelope().getSOAPConstants().getNextRoleURI();

      Iterator it = getChildren().iterator();
      while (it.hasNext())
      {
         SOAPHeaderElementAxisImpl she = (SOAPHeaderElementAxisImpl)it.next();
         if (she.getNamespaceURI().equals(namespace) &&
                 she.getName().equals(localPart))
         {

            if (!accessAllHeaders)
            {
               if (firstTime)
               {
                  // Do one-time setup
                  MessageContext mc = MessageContext.getCurrentContext();
                  if (mc != null)
                     actors = mc.getAxisEngine().getActorURIs();

                  firstTime = false;
               }

               String actor = she.getActor();
               if ((actor != null) && !nextActor.equals(actor) &&
                       (actors == null || !actors.contains(actor)))
               {
                  continue;
               }
            }

            v.addElement(she);
         }
      }

      return v.elements();
   }

   protected void outputImpl(SerializationContext context) throws Exception
   {
      boolean oldPretty = context.getPretty();
      context.setPretty(true);

      if (hasChildNodes())
      {
         // Output <SOAP-ENV:Header>
         context.startElement(new QName(soapConstants.getEnvelopeURI(),
                 Constants.ELEM_HEADER), null);

         Iterator it = getChildren().iterator();
         while (it.hasNext())
         {
            SOAPHeaderElementAxisImpl she = (SOAPHeaderElementAxisImpl)it.next();
            // Output this header element
            ((SOAPHeaderElementAxisImpl)she).output(context);
         }
         // Output </SOAP-ENV:Header>
         context.endElement();
      }

      context.setPretty(oldPretty);
   }

   /**
    * we have to override this to enforce that SOAPHeader immediate
    * children are exclusively of type SOAPHeaderElement (otherwise
    * we'll get mysterious ClassCastExceptions down the road... )
    *
    * @param element child element
    * @return soap element
    * @throws SOAPException
    */
   public SOAPElement addChildElement(SOAPElement element)
           throws SOAPException
   {
      if (!(element instanceof javax.xml.soap.SOAPHeaderElement))
      {
         throw new SOAPException(Messages.getMessage("badSOAPHeader00"));
      }
      return super.addChildElement(element);
   }

   public SOAPElement addChildElement(Name name) throws SOAPException
   {
      SOAPHeaderElementAxisImpl child = new SOAPHeaderElementAxisImpl(name.getURI(),
              name.getLocalName());
      addChild(child);
      child.setEnvelope(getEnvelope());
      return child;
   }

   public SOAPElement addChildElement(String localName) throws SOAPException
   {
      // Inherit parent's namespace
      SOAPHeaderElementAxisImpl child = new SOAPHeaderElementAxisImpl(getNamespaceURI(),
              localName);
      addChild(child);
      child.setEnvelope(getEnvelope());
      return child;
   }

   public SOAPElement addChildElement(String localName,
                                      String prefix) throws SOAPException
   {
      SOAPHeaderElementAxisImpl child = new SOAPHeaderElementAxisImpl(getNamespaceURI(prefix),
              localName);
      addChild(child);
      child.setEnvelope(getEnvelope());
      return child;
   }

   public SOAPElement addChildElement(String localName,
                                      String prefix,
                                      String uri) throws SOAPException
   {
      SOAPHeaderElementAxisImpl child = new SOAPHeaderElementAxisImpl(uri,
              localName);
      child.setPrefix(prefix);
      child.addNamespaceDeclaration(prefix, uri);
      addChild(child);
      child.setEnvelope(getEnvelope());
      return child;
   }
}