/*
 * 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.jboss.axis.message;

import org.jboss.axis.AxisFault;
import org.jboss.axis.Constants;
import org.jboss.axis.encoding.Callback;
import org.jboss.axis.encoding.CallbackTarget;
import org.jboss.axis.encoding.DeserializationContext;
import org.jboss.axis.encoding.Deserializer;
import org.jboss.axis.soap.SOAPConstants;
import org.jboss.axis.utils.ClassUtils;
import org.jboss.axis.utils.Messages;
import org.jboss.logging.Logger;
import org.w3c.dom.Element;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

import javax.xml.namespace.QName;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;

/**
 * Build a Fault body element.
 *
 * @author Sam Ruby (rubys@us.ibm.com)
 * @author Glen Daniels (gdaniels@apache.org)
 * @author Tom Jordahl (tomj@macromedia.com)
 */
public class SOAPFaultBuilder extends SOAPHandler implements Callback
{
   boolean waiting = false;
   boolean passedEnd = false;

   protected SOAPFaultImpl element;
   protected DeserializationContext context;
   static HashMap fields_soap11 = new HashMap();
   static HashMap fields_soap12 = new HashMap();

   // Fault data
   protected QName faultCode = null;
   protected QName[] faultSubCode = null;
   protected String faultString = null;
   protected String faultActor = null;
   protected Element[] faultDetails;
   protected String faultNode = null;

   protected SOAPFaultCodeBuilder code;

   protected Class faultClass = null;
   protected Object faultData = null;

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

   static
   {
      fields_soap11.put(Constants.ELEM_FAULT_CODE, Constants.XSD_QNAME);
      fields_soap11.put(Constants.ELEM_FAULT_STRING, Constants.XSD_STRING);
      fields_soap11.put(Constants.ELEM_FAULT_ACTOR, Constants.XSD_STRING);
      fields_soap11.put(Constants.ELEM_FAULT_DETAIL, null);
   }

   static
   {
      fields_soap12.put(Constants.ELEM_FAULT_REASON_SOAP12, null);
      fields_soap12.put(Constants.ELEM_FAULT_ROLE_SOAP12, Constants.XSD_STRING);
      fields_soap12.put(Constants.ELEM_FAULT_NODE_SOAP12, Constants.XSD_STRING);
      fields_soap12.put(Constants.ELEM_FAULT_DETAIL_SOAP12, null);
   }

   public SOAPFaultBuilder(SOAPFaultImpl element,
                           DeserializationContext context)
   {
      this.element = element;
      this.context = context;
   }

   public void startElement(String namespace, String localName,
                            String prefix, Attributes attributes,
                            DeserializationContext context)
           throws SAXException
   {
      SOAPConstants soapConstants = Constants.DEFAULT_SOAP_VERSION;
      if (context.getMessageContext() != null)
         soapConstants = context.getMessageContext().getSOAPConstants();

      if (soapConstants == SOAPConstants.SOAP12_CONSTANTS &&
              attributes.getValue(Constants.URI_SOAP12_ENV, Constants.ATTR_ENCODING_STYLE) != null)
      {

         AxisFault fault = new AxisFault(Constants.FAULT_SOAP12_SENDER,
                 null, Messages.getMessage("noEncodingStyleAttrAppear", "Fault"), null, null, null);

         throw new SAXException(fault);
      }

      super.startElement(namespace, localName, prefix, attributes, context);
   }

   void setFaultData(Object data)
   {
      faultData = data;
      if (waiting && passedEnd)
      {
         // This happened after the end of the <soap:Fault>, so make
         // sure we set up the fault.
         createFault();
      }
      waiting = false;
   }

   public void setFaultClass(Class faultClass)
   {
      this.faultClass = faultClass;
   }

   /**
    * Final call back where we can populate the exception with data.
    */
   public void endElement(String namespace, String localName,
                          DeserializationContext context)
           throws SAXException
   {
      super.endElement(namespace, localName, context);
      if (!waiting)
      {
         createFault();
      }
      else
      {
         passedEnd = true;
      }
   }

   void setWaiting(boolean waiting)
   {
      this.waiting = waiting;
   }

   /**
    * When we're sure we have everything, this gets called.
    */
   private void createFault()
   {
      AxisFault f = null;

      SOAPConstants soapConstants = context.getMessageContext() == null ?
              SOAPConstants.SOAP11_CONSTANTS :
              context.getMessageContext().getSOAPConstants();

      if (faultClass != null)
      {
         // Custom fault handling
         try
         {

            // If we have an element which is fault data, It can be:
            // 1. A simple type that needs to be passed in to the constructor
            // 2. A complex type that is the exception itself
            if (faultData != null)
            {
               if (faultData instanceof AxisFault)
               {
                  // This is our exception class
                  f = (AxisFault)faultData;
               }
               // If the faultData is of the expected faultClass type
               else if (faultClass.isAssignableFrom(faultData.getClass()))
               {
                  f = AxisFault.makeFault((Exception)faultData);
               }

               // Create the exception, passing the data to the constructor.
               if (f == null)
               {
                  try
                  {
                     Constructor ctor = faultClass.getConstructor(new Class[]{faultData.getClass()});
                     f = (AxisFault)ctor.newInstance(new Object[]{faultData});
                  }
                  catch (Exception ignore)
                  {
                     // Don't do anything here
                  }
               }

               // Create the exception, passing the data to the constructor.
               if (f == null && Exception.class.isAssignableFrom(faultData.getClass()))
               {
                  // if an exception, try to construct it using known parameters
                  try
                  {
                     Constructor ctor = faultClass.getConstructor(new Class[]{String.class});
                     Exception custEx = (Exception)ctor.newInstance(new Object[]{((Exception)faultData).getMessage()});
                     f = AxisFault.makeFault(custEx);
                  }
                  catch (Exception ignore)
                  {
                     // Don't do anything here
                  }
               }

            }

            // If we have an AxisFault, set the fields
            if (AxisFault.class.isAssignableFrom(faultClass))
            {
               if (f == null)
               {
                  // this is to support the <exceptionName> detail
                  f = (AxisFault)faultClass.newInstance();
               }

               if (soapConstants == SOAPConstants.SOAP12_CONSTANTS)
               {
                  f.setFaultCode(code.getFaultCode());

                  SOAPFaultCodeBuilder c = code;
                  while ((c = c.getNext()) != null)
                  {
                     f.addFaultSubCode(c.getFaultCode());
                  }
               }
               else
               {
                  f.setFaultCode(faultCode);
               }

               f.setFaultString(faultString);
               f.setFaultActor(faultActor);
               f.setFaultNode(faultNode);
               f.setFaultDetail(faultDetails);
            }
         }
         catch (Exception e)
         {
            // Don't do anything here, since a problem above means
            // we'll just fall through and use a plain AxisFault.
         }
      }

      // if faultClass null and faultString is not, try to create from that.
      if (faultClass == null && faultString != null && faultString.indexOf(' ') < 0)
      {
         try
         {
            // if is an exception, try to construct it using known parameters
            Class argClass = ClassUtils.forName(faultString);

            Constructor[] constructs = argClass.getConstructors();
            for (int x = 0; x < constructs.length; x++)
            {
               // try to find just string parameter constructor
               Constructor construct = constructs[x];
               Class[] params = construct.getParameterTypes();
               if (params.length == 1 && params[0].isAssignableFrom(String.class))
               {
                  Exception custEx = (Exception)construct.newInstance(new Object[]{""});
                  f = AxisFault.makeFault(custEx);
                  break;
               }
            }
         }
         catch (Exception e)
         {
            log.debug("Cannot create a fault instance of class:  " + faultString);
         }
      }


      if (f == null)
      {
         if (soapConstants == SOAPConstants.SOAP12_CONSTANTS)
         {
            faultCode = code.getFaultCode();
            if (code.getNext() != null)
            {
               Vector v = new Vector();

               SOAPFaultCodeBuilder c = code;
               while ((c = c.getNext()) != null)
                  v.add(c.getFaultCode());

               faultSubCode = (QName[])v.toArray(new QName[v.size()]);
            }
         }

         f = new AxisFault(faultCode,
                 faultSubCode,
                 faultString,
                 faultActor,
                 faultNode,
                 faultDetails);

         try
         {
            Vector headers = element.getEnvelope().getHeaders();
            for (int i = 0; i < headers.size(); i++)
            {
               SOAPHeaderElementAxisImpl header =
                       (SOAPHeaderElementAxisImpl)headers.elementAt(i);
               f.addHeader(header);
            }
         }
         catch (AxisFault axisFault)
         {
            // What to do here?
         }
      }

      element.setFault(f);
   }

   public SOAPHandler onStartChild(String namespace,
                                   String name,
                                   String prefix,
                                   Attributes attributes,
                                   DeserializationContext context)
           throws SAXException
   {
      SOAPHandler retHandler = null;

      SOAPConstants soapConstants = context.getMessageContext() == null ?
              SOAPConstants.SOAP11_CONSTANTS :
              context.getMessageContext().getSOAPConstants();

      QName qName = null;
      // If we found the type for this field, get the deserializer
      // otherwise, if this is the details element, use the special
      // SOAPFaultDetailsBuilder handler to take care of custom fault data
      if (soapConstants == SOAPConstants.SOAP12_CONSTANTS)
      {
         qName = (QName)fields_soap12.get(name);
         if (qName == null)
         {
            QName thisQName = new QName(namespace, name);
            if (thisQName.equals(Constants.QNAME_FAULTCODE_SOAP12))
               return (code = new SOAPFaultCodeBuilder());
            else if (thisQName.equals(Constants.QNAME_FAULTREASON_SOAP12))
               return new SOAPFaultReasonBuilder(this);
            else if (thisQName.equals(Constants.QNAME_FAULTDETAIL_SOAP12))
               return new SOAPFaultDetailsBuilder(this);

         }
      }
      else
      {
         qName = (QName)fields_soap11.get(name);
         if (qName == null && name.equals(Constants.ELEM_FAULT_DETAIL))
            return new SOAPFaultDetailsBuilder(this);
      }

      if (qName != null)
      {
         Deserializer currentDeser = context.getDeserializerForType(qName);
         if (currentDeser != null)
         {
            currentDeser.registerValueTarget(new CallbackTarget(this, new QName(namespace, name)));
         }
         retHandler = (SOAPHandler)currentDeser;
      }

      return retHandler;
   }

   public void onEndChild(String namespace, String localName,
                          DeserializationContext context)
           throws SAXException
   {
      if (Constants.ELEM_FAULT_DETAIL.equals(localName))
      {
         SOAPElementAxisImpl el = context.getCurElement();
         List children = el.getChildren();
         if (children != null)
         {
            Element[] elements = new Element[children.size()];
            for (int i = 0; i < elements.length; i++)
            {
               try
               {
                  elements[i] = ((SOAPElementAxisImpl)children.get(i)).
                          getAsDOM();

               }
               catch (Exception e)
               {
                  throw new SAXException(e);
               }
            }
            faultDetails = elements;
         }
      }
   }

   /*
    * Defined by Callback.
    * This method gets control when the callback is invoked.
    * @param is the value to set.
    * @param hint is an Object that provide additional hint information.
    */
   public void setValue(Object value, Object hint)
   {
      String local = ((QName)hint).getLocalPart();
      if (((QName)hint).getNamespaceURI().equals(Constants.URI_SOAP12_ENV))
      {
         if (local.equals(Constants.ELEM_FAULT_ROLE_SOAP12))
         {
            faultActor = (String)value;
         }
         else if (local.equals(Constants.ELEM_TEXT_SOAP12))
         {
            faultString = (String)value;
         }
         else if (local.equals(Constants.ELEM_FAULT_NODE_SOAP12))
         {
            faultNode = (String)value;
         }
      }
      else
      {
         if (local.equals(Constants.ELEM_FAULT_CODE))
         {
            faultCode = (QName)value;
         }
         else if (local.equals(Constants.ELEM_FAULT_STRING))
         {
            faultString = (String)value;
         }
         else if (local.equals(Constants.ELEM_FAULT_ACTOR))
         {
            faultActor = (String)value;
         }
      }

   }

   /**
    * A simple map of holder objects and their primitive types
    */
   private static HashMap TYPES = new HashMap(7);

   static
   {
      TYPES.put(java.lang.Integer.class, int.class);
      TYPES.put(java.lang.Float.class, float.class);
      TYPES.put(java.lang.Boolean.class, boolean.class);
      TYPES.put(java.lang.Double.class, double.class);
      TYPES.put(java.lang.Byte.class, byte.class);
      TYPES.put(java.lang.Short.class, short.class);
      TYPES.put(java.lang.Long.class, long.class);
   }

   /**
    * Internal method to convert wrapper classes to their base class
    */
   private Class ConvertWrapper(Class cls)
   {
      Class ret = (Class)TYPES.get(cls);
      if (ret != null)
      {
         return ret;
      }
      return cls;
   }
}