/*
 * Copyright 2001-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.apache.axis.message;

import org.apache.axis.AxisFault;
import org.apache.axis.Constants;
import org.apache.axis.description.FaultDesc;
import org.apache.axis.description.OperationDesc;
import org.apache.axis.encoding.DeserializationContext;
import org.apache.axis.encoding.SerializationContext;
import org.apache.axis.soap.SOAPConstants;
import org.apache.axis.utils.Messages;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.AttributesImpl;

import javax.xml.namespace.QName;
import javax.xml.soap.DetailEntry;
import javax.xml.soap.Name;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPFault;
import java.io.IOException;
import java.util.List;
import java.util.Locale;

/**
 * A Fault body element.
 *
 * @author Sam Ruby (rubys@us.ibm.com)
 * @author Glen Daniels (gdaniels@macromedia.com)
 * @author Tom Jordahl (tomj@macromedia.com)
 */
public class SOAPFaultImpl extends SOAPBodyElementAxisImpl implements SOAPFault
{
   protected AxisFault fault;
   protected String prefix;
   private java.util.Locale locale;

   public SOAPFaultImpl(String namespace, String localName, String prefix,
                        Attributes attrs, DeserializationContext context)
           throws AxisFault
   {
      super(namespace, localName, prefix, attrs, context);
   }

   public SOAPFaultImpl(AxisFault fault)
   {
      this.fault = fault;
   }

   public void outputImpl(SerializationContext context)
           throws IOException
   {
      SOAPConstants soapConstants = context.getMessageContext() == null ?
              SOAPConstants.SOAP11_CONSTANTS :
              context.getMessageContext().getSOAPConstants();

      namespaceURI = soapConstants.getEnvelopeURI();
      name = Constants.ELEM_FAULT;

      context.registerPrefixForURI(prefix, soapConstants.getEnvelopeURI());
      context.startElement(new QName(this.getNamespaceURI(),
              this.getName()),
              attributes);

      // XXX - Can fault be anything but an AxisFault here?
      if (fault instanceof AxisFault)
      {
         AxisFault axisFault = fault;
         if (axisFault.getFaultCode() != null)
         {
            // Do this BEFORE starting the element, so the prefix gets
            // registered if needed.
            if (soapConstants == SOAPConstants.SOAP12_CONSTANTS)
            {
               String faultCode = context.qName2String(axisFault.getFaultCode());
               context.startElement(Constants.QNAME_FAULTCODE_SOAP12, null);
               context.startElement(Constants.QNAME_FAULTVALUE_SOAP12, null);
               context.writeSafeString(faultCode);
               context.endElement();
               QName[] subcodes = axisFault.getFaultSubCodes();
               if (subcodes != null)
               {
                  for (int i = 0; i < subcodes.length; i++)
                  {
                     faultCode = context.qName2String(subcodes[i]);
                     context.startElement(Constants.QNAME_FAULTSUBCODE_SOAP12, null);
                     context.startElement(Constants.QNAME_FAULTVALUE_SOAP12, null);
                     context.writeSafeString(faultCode);
                     context.endElement();
                  }

                  for (int i = 0; i < subcodes.length; i++)
                     context.endElement();

               }
               context.endElement();
            }
            else
            {
               String faultCode = context.qName2String(axisFault.getFaultCode());
               context.startElement(Constants.QNAME_FAULTCODE, null);
               context.writeSafeString(faultCode);
               context.endElement();
            }
         }

         if (axisFault.getFaultString() != null)
         {
            if (soapConstants == SOAPConstants.SOAP12_CONSTANTS)
            {
               context.startElement(Constants.QNAME_FAULTREASON_SOAP12, null);
               AttributesImpl attrs = new AttributesImpl();
               attrs.addAttribute("http://www.w3.org/XML/1998/namespace", "lang", "xml:lang", "CDATA", "en");
               context.startElement(Constants.QNAME_TEXT_SOAP12, attrs);
            }
            else
               context.startElement(Constants.QNAME_FAULTSTRING, null);
            context.writeSafeString(axisFault.getFaultString());
            context.endElement();
            if (soapConstants == SOAPConstants.SOAP12_CONSTANTS)
            {
               context.endElement();
            }
         }

         if (axisFault.getFaultActor() != null)
         {
            if (soapConstants == SOAPConstants.SOAP12_CONSTANTS)
               context.startElement(Constants.QNAME_FAULTROLE_SOAP12, null);
            else
               context.startElement(Constants.QNAME_FAULTACTOR, null);

            context.writeSafeString(axisFault.getFaultActor());
            context.endElement();
         }

         if (axisFault.getFaultNode() != null)
         {
            if (soapConstants == SOAPConstants.SOAP12_CONSTANTS)
            {
               context.startElement(Constants.QNAME_FAULTNODE_SOAP12, null);
               context.writeSafeString(axisFault.getFaultNode());
               context.endElement();
            }
         }

         // get the QName for this faults detail element
         QName qname = getFaultQName(fault.getClass(), context);

         if (qname == null && fault.detail != null)
         {
            Class detailClass = fault.detail.getClass();
            qname = getFaultQName(detailClass, context);
         }

         if (qname == null)
         {
            // not the greatest, but...
            qname = new QName("", "faultData");
         }

         Element[] faultDetails = axisFault.getFaultDetails();
         if (faultDetails != null)
         {
            if (soapConstants == SOAPConstants.SOAP12_CONSTANTS)
               context.startElement(Constants.QNAME_FAULTDETAIL_SOAP12, null);
            else
               context.startElement(Constants.QNAME_FAULTDETAILS, null);

            // Allow the fault to write its data, if any
            axisFault.writeDetails(qname, context);

            // Then output any other elements
            for (int i = 0; i < faultDetails.length; i++)
            {
               context.writeDOMElement(faultDetails[i]);
            }
            context.endElement();
         }
      }

      context.endElement();
   }

   private QName getFaultQName(Class cls, SerializationContext context)
   {
      FaultDesc faultDesc = getFaultDesc(cls, context);
      if (faultDesc != null)
      {
         return faultDesc.getQName();
      }
      return null;
   }

   private QName getFaultXMLType(Class cls, SerializationContext context)
   {
      FaultDesc faultDesc = getFaultDesc(cls, context);
      if (faultDesc != null)
      {
         return faultDesc.getXmlType();
      }
      return null;
   }

   private FaultDesc getFaultDesc(Class cls, SerializationContext context)
   {
      FaultDesc faultDesc = null;
      if (!cls.equals(AxisFault.class))
      {
         if (context.getMessageContext() != null)
         {
            OperationDesc op = context.getMessageContext().getOperation();
            if (op != null)
            {
               faultDesc = op.getFaultByClass(cls);
            }
         }
      }
      return faultDesc;
   }

   public AxisFault getFault
           ()
   {
      return fault;
   }

   public void setFault
           (AxisFault
           fault)
   {
      this.fault = fault;
   }

   /**
    * Sets this <CODE>SOAPFaultException</CODE> object with the given
    * fault code.
    * <p/>
    * <P>Fault codes, which given information about the fault,
    * are defined in the SOAP 1.1 specification.</P>
    *
    * @param faultCode a <CODE>String</CODE> giving
    *                  the fault code to be set; must be one of the fault codes
    *                  defined in the SOAP 1.1 specification
    * @throws SOAPException if there was an error in
    *                       adding the <CODE>faultCode</CODE> to the underlying XML
    *                       tree.
    */
   public void setFaultCode
           (String
           faultCode) throws SOAPException
   {
      fault.setFaultCodeAsString(faultCode);
   }

   /**
    * Gets the fault code for this <CODE>SOAPFaultException</CODE>
    * object.
    *
    * @return a <CODE>String</CODE> with the fault code
    */
   public String getFaultCode
           ()
   {
      return fault.getFaultCode().getLocalPart();
   }

   /**
    * Sets this <CODE>SOAPFaultException</CODE> object with the given
    * fault actor.
    * <p/>
    * <P>The fault actor is the recipient in the message path who
    * caused the fault to happen.</P>
    *
    * @param faultActor a <CODE>String</CODE>
    *                   identifying the actor that caused this <CODE>
    *                   SOAPFaultException</CODE> object
    * @throws SOAPException if there was an error in
    *                       adding the <CODE>faultActor</CODE> to the underlying XML
    *                       tree.
    */
   public void setFaultActor
           (String
           faultActor) throws SOAPException
   {
      fault.setFaultActor(faultActor);
   }

   /**
    * Gets the fault actor for this <CODE>SOAPFaultException</CODE>
    * object.
    *
    * @return a <CODE>String</CODE> giving the actor in the message
    *         path that caused this <CODE>SOAPFaultException</CODE> object
    * @see #setFaultActor(java.lang.String) setFaultActor(java.lang.String)
    */
   public String getFaultActor
           ()
   {
      return fault.getFaultActor();
   }

   /**
    * Sets the fault string for this <CODE>SOAPFaultException</CODE>
    * object to the given string.
    *
    * @param faultString a <CODE>String</CODE>
    *                    giving an explanation of the fault
    * @throws SOAPException if there was an error in
    *                       adding the <CODE>faultString</CODE> to the underlying XML
    *                       tree.
    * @see #getFaultString() getFaultString()
    */
   public void setFaultString
           (String
           faultString) throws SOAPException
   {
      fault.setFaultString(faultString);
   }

   /**
    * Gets the fault string for this <CODE>SOAPFaultException</CODE>
    * object.
    *
    * @return a <CODE>String</CODE> giving an explanation of the
    *         fault
    */
   public String getFaultString
           ()
   {
      return fault.getFaultString();
   }

   /**
    * Returns the detail element for this <CODE>SOAPFaultException</CODE>
    * object.
    * <p/>
    * <P>A <CODE>Detail</CODE> object carries
    * application-specific error information related to <CODE>
    * SOAPBodyElement</CODE> objects.</P>
    *
    * @return a <CODE>Detail</CODE> object with
    *         application-specific error information
    */
   public javax.xml.soap.Detail getDetail
           ()
   {

      List children = this.getChildren();
      if (children == null || children.size() <= 0)
         return null;

      // find detail element
      for (int i = 0; i < children.size(); i++)
      {
         Object obj = children.get(i);
         if (obj instanceof javax.xml.soap.Detail)
         {
            return (javax.xml.soap.Detail)obj;
         }
      }
      return null;
   }

   /**
    * Creates a <CODE>Detail</CODE> object and sets it as the
    * <CODE>Detail</CODE> object for this <CODE>SOAPFaultException</CODE>
    * object.
    * <p/>
    * <P>It is illegal to add a detail when the fault already
    * contains a detail. Therefore, this method should be called
    * only after the existing detail has been removed.</P>
    *
    * @return the new <CODE>Detail</CODE> object
    * @throws SOAPException if this
    *                       <CODE>SOAPFaultException</CODE> object already contains a valid
    *                       <CODE>Detail</CODE> object
    */
   public javax.xml.soap.Detail addDetail
           () throws SOAPException
   {
      if (getDetail() != null)
      {
         throw new SOAPException(Messages.getMessage("valuePresent"));
      }
      DetailImpl detail = convertToDetail(fault);
      addChildElement(detail);
      return detail;
   }

   public void setFaultCode
           (Name
           faultCodeQName) throws SOAPException
   {
      String uri = faultCodeQName.getURI();
      String local = faultCodeQName.getLocalName();
      String prefix = faultCodeQName.getPrefix();

      this.prefix = prefix;
      QName qname = new QName(uri, local);
      fault.setFaultCode(qname);
   }

   public Name getFaultCodeAsName
           ()
   {
      QName qname = fault.getFaultCode();
      String uri = qname.getNamespaceURI();
      String local = qname.getLocalPart();
      return new PrefixedQName(uri, local, prefix);
   }

   public void setFaultString
           (String
           faultString, Locale
           locale) throws SOAPException
   {
      fault.setFaultString(faultString);
      this.locale = locale;
   }

   public Locale getFaultStringLocale
           ()
   {
      return locale;
   }

   /**
    * Convert the details in an AxisFault to a Detail object
    *
    * @param fault source of the fault details
    * @return a detail element contructed from the AxisFault details
    * @throws SOAPException
    */
   private static DetailImpl convertToDetail
           (AxisFault
           fault)
           throws SOAPException
   {
      DetailImpl detail = new DetailImpl(fault);
      Element[] darray = fault.getFaultDetails();
      for (int i = 0; i < darray.length; i++)
      {
         Element detailtEntryElem = darray[i];
         DetailEntry detailEntry = detail.addDetailEntry(new PrefixedQName(detailtEntryElem.getNamespaceURI(),
                 detailtEntryElem.getLocalName(), detailtEntryElem.getPrefix()));
         copyChildren(detailEntry, detailtEntryElem);
      }
      return detail;
   }

   /**
    * Copy the children of a DOM element to a SOAPElement.
    *
    * @param soapElement target of the copy
    * @param domElement  source for the copy
    * @throws SOAPException
    */
   private static void copyChildren
           (SOAPElement
           soapElement, Element
           domElement)
           throws SOAPException
   {
      org.w3c.dom.NodeList nl = domElement.getChildNodes();
      for (int j = 0; j < nl.getLength(); j++)
      {
         org.w3c.dom.Node childNode = nl.item(j);
         if (childNode.getNodeType() == Node.TEXT_NODE)
         {
            soapElement.addTextNode(childNode.getNodeValue());
            break; // only one text node assmed
         }
         if (childNode.getNodeType() == Node.ELEMENT_NODE)
         {
            String uri = childNode.getNamespaceURI();
            SOAPElement childSoapElement = null;
            if (uri == null)
            {
               childSoapElement = soapElement.addChildElement(childNode.getLocalName
                       ());
            }
            else
            {
               childSoapElement = soapElement.addChildElement(childNode.getLocalName(),
                       childNode.getPrefix(), uri);
            }
            copyChildren(childSoapElement, (Element)childNode);
         }
      }
   }
}
