/*
 * 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 ;

import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Vector;
import java.io.PrintWriter;
import java.io.StringWriter;

import javax.xml.namespace.QName;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.rpc.soap.SOAPFaultException;

import org.apache.axis.components.logger.LogFactory;
import org.apache.axis.encoding.SerializationContext;
import org.apache.axis.message.SOAPEnvelope;
import org.apache.axis.message.SOAPFault;
import org.apache.axis.message.SOAPHeaderElement;
import org.apache.axis.soap.SOAPConstants;
import org.apache.axis.utils.JavaUtils;
import org.apache.axis.utils.XMLUtils;
import org.apache.axis.utils.NetworkUtils;
import org.apache.commons.logging.Log;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;

/**
 * An exception which maps cleanly to a SOAP fault.
 * This is a base class for exceptions which are mapped to faults.
 * SOAP faults contain
 * <ol>
 * <li>A fault string
 * <li>A fault code
 * <li>A fault actor
 * <li>Fault details; an xml tree of fault specific stuff
 * </ol>
 * @author Doug Davis (dug@us.ibm.com)
 * @author James Snell (jasnell@us.ibm.com)
 * @author Steve Loughran
 */

public class AxisFault extends java.rmi.RemoteException {
    /**
     * The <code>Log</code> used by this class for all logging.
     */
    protected static Log log =
        LogFactory.getLog(AxisFault.class.getName());

    protected QName     faultCode ;
    /** SOAP1.2 addition: subcodes of faults; a Vector of QNames */
    protected Vector    faultSubCode ;
    protected String    faultString = "";
    protected String    faultActor ;
    protected Vector    faultDetails ;  // vector of Element's
    protected String    faultNode ;

    /** SOAP headers which should be serialized with the Fault. */
    protected ArrayList faultHeaders = null;

    /**
     * Make an AxisFault based on a passed Exception.  If the Exception is
     * already an AxisFault, simply use that.  Otherwise, wrap it in an
     * AxisFault.  If the Exception is an InvocationTargetException (which
     * already wraps another Exception), get the wrapped Exception out from
     * there and use that instead of the passed one.
     *
     * @param e the <code>Exception</code> to build a fault for
     * @return  an <code>AxisFault</code> representing <code>e</code>
     */
    public static AxisFault makeFault(Exception e)
    {
        if (e instanceof InvocationTargetException) {
            Throwable t = ((InvocationTargetException)e).getTargetException();
            if (t instanceof Exception) {
                e = (Exception)t;
            }
        }

        if (e instanceof AxisFault) {
            return (AxisFault)e;
        }

        return new AxisFault(e);
    }

    /**
     * Make a fault in the <code>Constants.NS_URI_AXIS</code> namespace.
     *
     * @param code fault code which will be passed into the Axis namespace
     * @param faultString fault string
     * @param actor fault actor
     * @param details details; if null the current stack trace and classname is
     * inserted into the details.
     */
    public AxisFault(String code, String faultString,
                     String actor, Element[] details) {
        this(new QName(Constants.NS_URI_AXIS, code),
                faultString, actor, details);
    }

    /**
     * Make a fault in any namespace.
     *
     * @param code fault code which will be passed into the Axis namespace
     * @param faultString fault string
     * @param actor fault actor
     * @param details details; if null the current stack trace and classname is
     * inserted into the details.
     */
    public AxisFault(QName code, String faultString,
                     String actor, Element[] details) {
        super (faultString);
        setFaultCode( code );
        setFaultString( faultString );
        setFaultActor( actor );
        setFaultDetail( details );
        if (details == null) {
            initFromException(this);
        }
    }

    /**
     * Make a fault in any namespace.
     *
     * @param code fault code which will be passed into the Axis namespace
     * @param subcodes fault subcodes which will be pased into the Axis namespace
     * @param faultString fault string
     * @param actor fault actor, same as fault role in SOAP 1.2
     * @param node which node caused the fault on the SOAP path
     * @param details details; if null the current stack trace and classname is
     * inserted into the details.
     * @since axis1.1
     */
    public AxisFault(QName code, QName[] subcodes, String faultString,
                     String actor, String node, Element[] details) {
        super (faultString);
        setFaultCode( code );
        if (subcodes != null) {
            for (int i = 0; i < subcodes.length; i++) {
                addFaultSubCode( subcodes[i] );
            }
        }
        setFaultString( faultString );
        setFaultActor( actor );
        setFaultNode( node );
        setFaultDetail( details );
        if (details == null) {
            initFromException(this);
        }
    }

    // fixme: docs says private, access says protected
    /**
     * Wrap an AxisFault around an existing Exception. This is private
     * to force everyone to use makeFault() above, which sanity-checks us.
     *
     * @param target  the target <code>Exception</code>
     */
    protected AxisFault(Exception target) {
        super ("", target);
        // ? SOAP 1.2 or 1.1 ?
        setFaultCodeAsString( Constants.FAULT_SERVER_USER );
        initFromException(target);

        // if the target is a JAX-RPC SOAPFaultException init
        // AxisFault with the values from the SOAPFaultException
        if ( target instanceof SOAPFaultException ) {
            //strip out the hostname as we want any new one
            removeHostname();
            initFromSOAPFaultException((SOAPFaultException) target);
            //but if they left it out, add it
            addHostnameIfNeeded();
        }

    }

    /**
     * create a simple axis fault from the message. Classname and stack trace
     * go into the fault details.
     * @param message
     */
    public AxisFault(String message)
    {
        super (message);
        setFaultCodeAsString(Constants.FAULT_SERVER_GENERAL);
        setFaultString(message);
        initFromException(this);
    }

    /**
     * No-arg constructor for building one from an XML stream.
     */
    public AxisFault()
    {
        super();
        setFaultCodeAsString(Constants.FAULT_SERVER_GENERAL);
        initFromException(this);
    }

    /**
     * create a fault from any throwable;
     * When faulting a throwable (as opposed to an exception),
     * stack trace information does not go into the fault.
     * @param message any extra text to with the fault
     * @param t whatever is to be turned into a fault
     */
    public AxisFault (String message, Throwable t)
    {
        super (message, t);
        setFaultCodeAsString(Constants.FAULT_SERVER_GENERAL);
        setFaultString(getMessage());
        addHostnameIfNeeded();
    }

    /**
     * fill in soap fault details from the exception, unless
     * this object already has a stack trace in its details. Which, given
     * the way this private method is invoked, is a pretty hard situation to ever achieve.
     * This method adds classname of the exception and the stack trace.
     * @param target what went wrong
     */
    private void initFromException(Exception target)
    {
        //look for old stack trace
        Element oldStackTrace = lookupFaultDetail(Constants.QNAME_FAULTDETAIL_STACKTRACE);
        if (oldStackTrace != null) {
            // todo: Should we replace it or just let it be?
            return;
        }

        // Set the exception message (if any) as the fault string
        setFaultString( target.toString() );


        // Put the exception class into the AXIS SPECIFIC HACK
        //  "exceptionName" element in the details.  This allows
        // us to get back a correct Java Exception class on the other side
        // (assuming they have it available).
        // NOTE: This hack is obsolete!  We now serialize exception data
        // and the other side uses *that* QName to figure out what exception
        // to use, because the class name may be completly different on the
        // client.
        if ((target instanceof AxisFault) &&
            (target.getClass() != AxisFault.class)) {
          addFaultDetail(Constants.QNAME_FAULTDETAIL_EXCEPTIONNAME,
                    target.getClass().getName());
        }

        //add stack trace
        if (target == this) {
            // only add stack trace. JavaUtils.stackToString() call would
            // include dumpToString() info which is already sent as different
            // elements of this fault.
            addFaultDetail(Constants.QNAME_FAULTDETAIL_STACKTRACE,
                           getPlainStackTrace());
        } else {
            addFaultDetail(Constants.QNAME_FAULTDETAIL_STACKTRACE,
                           JavaUtils.stackToString(target));
        }

        //add the hostname
        addHostnameIfNeeded();
    }
    
    /**
     * Initiates the AxisFault with the values from a SOAPFaultException
     * @param fault SOAPFaultException
     */
    private void initFromSOAPFaultException(SOAPFaultException fault) {
        
        // faultcode
        if ( fault.getFaultCode() != null ) {
            setFaultCode(fault.getFaultCode());
        }
        
        // faultstring
        if ( fault.getFaultString() != null ) {
            setFaultString(fault.getFaultString());
        }
        
        // actor
        if ( fault.getFaultActor() != null ) {
            setFaultActor(fault.getFaultActor());
        }

        if ( null == fault.getDetail() ) {
            return;
        }
        
        // We get an Iterator but we need a List
        Vector details = new Vector();       
        Iterator detailIter = fault.getDetail().getChildElements();
        while (detailIter.hasNext()) {
            details.add( detailIter.next());            
        }        
        
        // Convert the List in an Array an return the array 
        setFaultDetail( XMLUtils.asElementArray(details));                
    }

    /**
     * Init the fault details data structure; does nothing
     * if this exists already.
     */
    private void initFaultDetails() {
        if (faultDetails == null) {
            faultDetails = new Vector();
        }
    }

    /**
     * Clear the fault details list.
     */
    public void clearFaultDetails() {
        faultDetails=null;
    }

    /**
     * Dump the fault info to the log at debug level.
     */
    public void dump() {
        log.debug(dumpToString());
    }


    /**
     * turn the fault and details into a string, with XML escaping.
     * subclassers: for security (cross-site-scripting) reasons,
     * escape everything that could contain caller-supplied data.
     * @return stringified fault details
     */
    public String dumpToString()
    {
        StringBuffer buf = new StringBuffer("AxisFault");
        buf.append(JavaUtils.LS);
        buf.append(" faultCode: ");
        buf.append(XMLUtils.xmlEncodeString(faultCode.toString()));
        buf.append(JavaUtils.LS);
        buf.append(" faultSubcode: ");
        if (faultSubCode != null) {
            for (int i = 0; i < faultSubCode.size(); i++) {
                buf.append(JavaUtils.LS);
                buf.append(faultSubCode.elementAt(i).toString());
            }
        }
        buf.append(JavaUtils.LS);
        buf.append(" faultString: ");
        try {
        	buf.append(XMLUtils.xmlEncodeString(faultString));
        } catch (RuntimeException re) {
        	buf.append(re.getMessage());
        }
        buf.append(JavaUtils.LS);
        buf.append(" faultActor: ");
        buf.append(XMLUtils.xmlEncodeString(faultActor));
        buf.append(JavaUtils.LS);
        buf.append(" faultNode: ");
        buf.append(XMLUtils.xmlEncodeString(faultNode));
        buf.append(JavaUtils.LS);
        buf.append(" faultDetail: ");
        if (faultDetails != null) {
            for (int i=0; i < faultDetails.size(); i++) {
                Element e = (Element) faultDetails.get(i);
                buf.append(JavaUtils.LS);
                buf.append("\t{");
                buf.append(null == e.getNamespaceURI() ? "" : e.getNamespaceURI());
                buf.append("}");
                buf.append(null == e.getLocalName() ? "" : e.getLocalName());
                buf.append(":");
                buf.append(XMLUtils.getInnerXMLString(e));
            }
        }
        buf.append(JavaUtils.LS);
        return buf.toString();
    }

    /**
     * Set the fault code.
     *
     * @param code a new fault code
     */
    public void setFaultCode(QName code) {
        faultCode = code ;
    }

    /**
     * Set the fault code (as a String).
     *
     * @param code a new fault code
     * @deprecated expect to see this go away after 1.1, use
     *             setFaultCodeAsString instead!
     */

    public void setFaultCode(String code) {
        setFaultCodeAsString(code);
    }

    /**
     * set a fault code string that is turned into a qname
     * in the SOAP 1.1 or 1.2 namespace, depending on the current context
     * @param code fault code
     */
    public void setFaultCodeAsString(String code) {
        SOAPConstants soapConstants = MessageContext.getCurrentContext() == null ?
                                        SOAPConstants.SOAP11_CONSTANTS :
                                        MessageContext.getCurrentContext().getSOAPConstants();

        faultCode = new QName(soapConstants.getEnvelopeURI(), code);
    }

    /**
     * Get the fault code <code>QName</code>.
     *
     * @return fault code QName or null if there is none yet.
     */
    public QName getFaultCode() {
        return( faultCode );
    }

    /**
     * Add a fault sub-code with the local name <code>code</code> and namespace
     * <code>Constants.NS_URI_AXIS</code>.
     * This is new in SOAP 1.2, ignored in SOAP 1.1
     *
     * @param code  the local name of the code to add
     * @since axis1.1
     */
    public void addFaultSubCodeAsString(String code) {
        initFaultSubCodes();
        faultSubCode.add(new QName(Constants.NS_URI_AXIS, code));
    }

    /**
     * Do whatever is needed to create the fault subcodes
     * data structure, if it is needed.
     */
    protected void initFaultSubCodes() {
        if (faultSubCode == null) {
            faultSubCode = new Vector();
        }
    }

    /**
     * Add a fault sub-code.
     * This is new in SOAP 1.2, ignored in SOAP 1.1.
     *
     * @param code  the <code>QName</code> of the fault sub-code to add
     * @since axis1.1
     */
    public void addFaultSubCode(QName code) {
        initFaultSubCodes();
        faultSubCode.add(code);
    }

    /**
     * Clear all fault sub-codes.
     * This is new in SOAP 1.2, ignored in SOAP 1.1.
     *
     * @since axis1.1
     */
    public void clearFaultSubCodes() {
        faultSubCode = null;
    }

    /**
     * get the fault subcode list; only used in SOAP 1.2
     * @since axis1.1
     * @return null for no subcodes, or a QName array
     */
    public QName[] getFaultSubCodes() {
        if (faultSubCode == null) {
            return null;
        }
        QName[] q = new QName[faultSubCode.size()];
        return (QName[])faultSubCode.toArray(q);
    }


    /**
     * Set a fault string.
     * @param str new fault string; null is turned into ""
     */
    public void setFaultString(String str) {
        if (str != null) {
            faultString = str ;
        } else {
            faultString = "";
        }
    }

    /**
     * Get the fault string; this will never be null but may be the
     * empty string.
     *
     * @return a fault string
     */
    public String getFaultString() {
        return( faultString );
    }

    /**
     * This is SOAP 1.2 equivalent of {@link #setFaultString(java.lang.String)}.
     *
     * @param str  the fault reason as a <code>String</code>
     * @since axis1.1
     */
    public void setFaultReason(String str) {
        setFaultString(str);
    }

    /**
     * This is SOAP 1.2 equivalent of {@link #getFaultString()}.
     * @since axis1.1
     * @return the fault <code>String</code>
     */
    public String getFaultReason() {
        return getFaultString();
    }

    /**
     * Set the fault actor.
     *
     * @param actor fault actor
     */
    public void setFaultActor(String actor) {
        faultActor = actor ;
    }

    /**
     * get the fault actor
     * @return actor or null
     */
    public String getFaultActor() {
        return( faultActor );
    }

    /**
     * This is SOAP 1.2 equivalent of {@link #getFaultActor()}.
     * @since axis1.1
     * @return the name of the fault actor
     */
    public String getFaultRole() {
        return getFaultActor();
    }

    // fixme: both faultRole and faultActor refer to the other one - can we
    //  break the circularity here?
    /**
     * This is SOAP 1.2 equivalent of {@link #setFaultActor(java.lang.String)}.
     * @since axis1.1
     */
    public void setFaultRole(String role) {
        setFaultActor(role);
    }

    /**
     * Get the fault node.
     *
     * This is new in SOAP 1.2
     * @since axis1.1
     * @return
     */
    public String getFaultNode() {
        return( faultNode );
    }

    /**
     * Set the fault node.
     *
     * This is new in SOAP 1.2.
     *
     * @param node  a <code>String</code> representing the fault node
     * @since axis1.1
     */
    public void setFaultNode(String node) {
        faultNode = node;
    }

    /**
     * Set the fault detail element to the arrary of details.
     *
     * @param details list of detail elements, can be null
     */
    public void setFaultDetail(Element[] details) {
        if ( details == null ) {
            faultDetails=null;
            return ;
        }
        faultDetails = new Vector( details.length );
        for ( int loop = 0 ; loop < details.length ; loop++ ) {
            faultDetails.add( details[loop] );
        }
    }

    /**
     * set the fault details to a string element.
     * @param details XML fragment
     */
    public void setFaultDetailString(String details) {
        clearFaultDetails();
        addFaultDetailString(details);
    }

    /**
     * add a string tag to the fault details.
     * @param detail XML fragment
     */
    public void addFaultDetailString(String detail) {
        initFaultDetails();
        try {
            Document doc = XMLUtils.newDocument();
            Element element = doc.createElement("string");
            Text text = doc.createTextNode(detail);
            element.appendChild(text);
            faultDetails.add(element);
        } catch (ParserConfigurationException e) {
            // This should not occur
            throw new InternalException(e);
        }
    }

    /**
     * Append an element to the fault detail list.
     *
     * @param detail the new element to add
     * @since Axis1.1
     */
    public void addFaultDetail(Element detail) {
        initFaultDetails();
        faultDetails.add(detail);
    }

    /**
     * Create an element of the given qname and add it to the details.
     *
     * @param qname qname of the element
     * @param body string to use as body
     */
    public void addFaultDetail(QName qname,String body) {
        Element detail = XMLUtils.StringToElement(qname.getNamespaceURI(),
                qname.getLocalPart(),
                body);

        addFaultDetail(detail);
    }

    // fixme: should we be returning null for none or a zero length array?
    /**
     * Get all the fault details.
     *
     * @return an array of fault details, or null for none
     */
    public Element[] getFaultDetails() {
        if (faultDetails == null) {
            return null;
        }
        Element result[] = new Element[faultDetails.size()];
        for (int i=0; i<result.length; i++) {
            result[i] = (Element) faultDetails.elementAt(i);
        }
        return result;
    }

    /**
     * Find a fault detail element by its qname.
     * @param qname name of the node to look for
     * @return the matching element or null
     * @since axis1.1
     */
    public Element lookupFaultDetail(QName qname) {
        if (faultDetails != null) {
            //extract details from the qname. the empty namespace is represented
            //by the empty string
            String searchNamespace = qname.getNamespaceURI();
            String searchLocalpart = qname.getLocalPart();
            //now spin through the elements, seeking a match
            Iterator it=faultDetails.iterator();
            while (it.hasNext()) {
                Element e = (Element) it.next();
                String localpart= e.getLocalName();
                if(localpart==null) {
                    localpart=e.getNodeName();
                }
                String namespace= e.getNamespaceURI();
                if(namespace==null) {
                    namespace="";
                }
                //we match on matching namespace and local part; empty namespace
                //in an element may be null, which matches QName's ""
                if(searchNamespace.equals(namespace)
                    && searchLocalpart.equals(localpart)) {
                    return e;
                }
            }
        }
        return null;
    }

    /**
     * Find and remove a specified fault detail element.
     *
     * @param qname qualified name of detail
     * @return true if it was found and removed, false otherwise
     * @since axis1.1
     */
    public boolean removeFaultDetail(QName qname) {
        Element elt=lookupFaultDetail(qname);
        if(elt==null) {
            return false;
        } else {
            return faultDetails.remove(elt);
        }
    }

    /**
     * Add this fault and any needed headers to the output context.
     *
     * @param context
     * @throws Exception
     */
    public void output(SerializationContext context) throws Exception {

        SOAPConstants soapConstants = Constants.DEFAULT_SOAP_VERSION;
        if (context.getMessageContext() != null) {
            soapConstants = context.getMessageContext().getSOAPConstants();
        }

        SOAPEnvelope envelope = new SOAPEnvelope(soapConstants);

        SOAPFault fault = new SOAPFault(this);
        envelope.addBodyElement(fault);

        // add any headers we need
        if (faultHeaders != null) {
            for (Iterator i = faultHeaders.iterator(); i.hasNext();) {
                SOAPHeaderElement header = (SOAPHeaderElement) i.next();
                envelope.addHeader(header);
            }
        }

        envelope.output(context);
    }

    /**
     * Stringify this fault as the current fault string.
     *
     * @return the fault string, possibly the empty string, but never null
     */
    public String toString() {
        return faultString;
    }

    /**
     * Gets the stack trace as a string.
     */
    private String getPlainStackTrace() {
        StringWriter sw = new StringWriter(512);
        PrintWriter pw = new PrintWriter(sw); 
        super.printStackTrace(pw);
        pw.close();
        return sw.toString();
    }

    /**
     * The override of the base class method prints out the
     * fault info before the stack trace.
     *
     * @param ps where to print
     */
    public void printStackTrace(PrintStream ps) {
        ps.println(dumpToString());
        super.printStackTrace(ps);
    }

    /**
     * The override of the base class method prints out the
     * fault info before the stack trace.
     *
     * @param pw where to print
     */
    public void printStackTrace(java.io.PrintWriter pw) {
        pw.println(dumpToString());
        super.printStackTrace(pw);
    }

    /**
     * Add a SOAP header which should be serialized along with the
     * fault.
     *
     * @param header a SOAPHeaderElement containing some fault-relevant stuff
     */
    public void addHeader(SOAPHeaderElement header) {
        if (faultHeaders == null) {
            faultHeaders = new ArrayList();
        }
        faultHeaders.add(header);
    }

    /**
     * Get the SOAP headers associated with this fault.
     *
     * @return an ArrayList containing any headers associated with this fault
     */
    public ArrayList getHeaders() {
        return faultHeaders;
    }

    /**
     * Clear all fault headers.
     */
    public void clearHeaders() {
        faultHeaders = null;
    }


    /**
     * Writes any exception data to the faultDetails.
     *
     * This can be overridden (and is) by emitted exception clases.
     * The base implementation will attempt to serialize exception data the
     * fault was created from an Exception and a type mapping is found for it.
     *
     * @param qname the <code>QName</code> to write this under
     * @param context the <code>SerializationContext</code> to write this fault
     *              to
     * @throws java.io.IOException if we can't write ourselves for any reason
     */
    public void writeDetails(QName qname, SerializationContext context)
            throws java.io.IOException {
        Object detailObject = this.detail;
        if (detailObject == null) {
            return;
        }

        boolean haveSerializer = false;
        try {
            if (context.getTypeMapping().getSerializer(detailObject.getClass()) != null) {
                haveSerializer = true;
            }
        } catch (Exception e) {
            // swallow this exception, it means that we don't know how to serialize
            // the details.
        }
        if (haveSerializer) {
            boolean oldMR = context.getDoMultiRefs();
            context.setDoMultiRefs(false);
            context.serialize(qname, null, detailObject);
            context.setDoMultiRefs(oldMR);
        }
    }

    /**
     * add the hostname of the current system. This is very useful for
     * locating faults on a cluster.
     * @since Axis1.2
     */
    public void addHostnameIfNeeded() {
        //look for an existing declaration
        if(lookupFaultDetail(Constants.QNAME_FAULTDETAIL_HOSTNAME)!=null) {
            //and do nothing if it exists
            return;
        }
        addHostname(NetworkUtils.getLocalHostname());
    }

    /**
     * add the hostname string. If one already exists, remove it.
     * @param hostname string name of a host
     * @since Axis1.2
     */
    public void addHostname(String hostname) {
        //add the hostname
        removeHostname();
        addFaultDetail(Constants.QNAME_FAULTDETAIL_HOSTNAME,
                hostname);
    }

    /**
     * strip out the hostname on a message. This
     * is useful for security reasons.
     */
    public void removeHostname() {
        removeFaultDetail(Constants.QNAME_FAULTDETAIL_HOSTNAME);
    }
}
