blob: b49fbc2939cb031123297fff2644773f6060a363 [file] [log] [blame]
/*
* 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);
}
}