package gov.nih.nci.cagrid.common;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.Calendar;
import org.apache.axis.Constants;
import org.apache.axis.types.URI;
import org.apache.axis.utils.JavaUtils;
import org.globus.util.I18n;
import org.globus.wsrf.utils.AnyHelper;
import org.globus.wsrf.utils.Resources;
import org.oasis.wsrf.faults.BaseFaultType;
import org.oasis.wsrf.faults.BaseFaultTypeDescription;
import org.oasis.wsrf.faults.BaseFaultTypeErrorCode;
import org.w3c.dom.Element;
/**
* This class provides convenience functions around BaseFault API. It also
* provides a common way of including stack traces with Faults. A stack trace
* of a Fault is added as a chained BaseFault with an error code dialect
* attribute set to {@link #STACK_TRACE STACK_TRACE}. A regular Java
* exception is automatically converted into a BaseFault with the description
* of exception message and with a chained BaseFault with
* {@link #STACK_TRACE STACK_TRACE} error code dialect.
*/
public class FaultHelper {
private static final String LS =
System.getProperty("line.separator");
/**
* Stack trace error code URI
*/
public static final URI STACK_TRACE;
/**
* Exception error code URI
*/
public static final URI EXCEPTION;
private static I18n i18n = I18n.getI18n(Resources.class.getName());
static {
try {
STACK_TRACE = new URI("http://www.globus.org/fault/stacktrace");
EXCEPTION = new URI("http://www.globus.org/fault/exception");
} catch(Exception e) {
throw new RuntimeException(e.getMessage());
}
}
private BaseFaultType fault;
/**
* Creates <code>FaultHelper</code> with a fault.
* If the fault contains a stack trace it will be automatically converted
* into a chained BaseFault with an error code dialect attribute set to
* set to {@link #STACK_TRACE STACK_TRACE}.
*
* @param fault fault
*/
public FaultHelper(BaseFaultType fault) {
this(fault, true);
}
/**
* Creates <code>FaultHelper</code> with a fault.
*
* @param fault fault
* @param convertStackTrace if true and if the fault contains a stack trace
* it will be automatically converted into a chained BaseFault with
* an error code dialect attribute set to set to
* {@link #STACK_TRACE STACK_TRACE}.
*/
public FaultHelper(BaseFaultType fault, boolean convertStackTrace) {
if (fault == null) {
throw new IllegalArgumentException(i18n.getMessage(
"nullArgument", "fault"));
}
this.fault = fault;
if (convertStackTrace) {
addStackTraceFault();
}
// add timestamp automatically if not set
if (this.fault.getTimestamp() == null) {
this.fault.setTimestamp(Calendar.getInstance());
}
}
/**
* Gets the fault.
*/
public BaseFaultType getFault() {
return this.fault;
}
/**
* Returns all the descriptions of the fault as a simple string.
*/
public String getDescriptionAsString() {
BaseFaultTypeDescription [] desc = this.fault.getDescription();
if (desc == null) {
return null;
}
StringBuffer buf = new StringBuffer();
for (int i=0;i<desc.length;i++) {
buf.append(desc[i].get_value());
if (i+1<desc.length) {
buf.append(" / ");
}
}
return buf.toString();
}
/**
* Returns descriptions of the fault.
*
* @return the descriptions. Might be null.
*/
public String[] getDescription() {
BaseFaultTypeDescription [] desc = this.fault.getDescription();
if (desc == null) {
return null;
}
String [] description = new String[desc.length];
for (int i=0;i<description.length;i++) {
description[i] = desc[i].get_value();
}
return description;
}
/**
* Sets the description of the fault.
*
* @param description the new description of the fault.
*/
public void setDescription(String description) {
setDescription((description == null) ?
null : new String [] {description});
}
/**
* Sets the description of the fault.
*
* @param description the new descriptions of the fault.
*/
public void setDescription(String[] description) {
BaseFaultTypeDescription [] desc = null;
if (description != null) {
desc = new BaseFaultTypeDescription[description.length];
for (int i=0;i<description.length;i++) {
desc[i] = new BaseFaultTypeDescription(description[i]);
}
}
this.fault.setDescription(desc);
}
/**
* Adds a description to the description list of the fault.
*
* @param description the description to add.
*/
public void addDescription(String description) {
if (description == null) {
// not throwing an exception when this can be properly handled
// throw new IllegalArgumentException(i18n.getMessage(
// "nullArgument", "description"));
description = "null";
}
BaseFaultTypeDescription [] desc = this.fault.getDescription();
BaseFaultTypeDescription [] newDesc = null;
if (desc == null) {
newDesc = new BaseFaultTypeDescription[1];
} else {
newDesc = new BaseFaultTypeDescription[desc.length + 1];
System.arraycopy(desc, 0, newDesc, 0, desc.length);
}
newDesc[newDesc.length - 1] =
new BaseFaultTypeDescription(description);
this.fault.setDescription(newDesc);
}
/**
* Adds a fault cause to the fault.
*
* @param exception the exception to add as a cause of this fault.
* If the exception is of BaseFault type then it is
* just added as is as a fault cause. Otherwise, the
* exception is converted into a new BaseFault and then
* added as a fault cause.
*/
public void addFaultCause(Throwable exception) {
addFaultCause( toBaseFault(exception) );
}
private void addFaultCause(BaseFaultType faultCause) {
BaseFaultType[] cause = this.fault.getFaultCause();
BaseFaultType[] newCause = null;
if (cause == null) {
newCause = new BaseFaultType[1];
} else {
newCause = new BaseFaultType [cause.length + 1];
System.arraycopy(cause, 0, newCause, 0, cause.length);
}
newCause[newCause.length - 1] = faultCause;
this.fault.setFaultCause(newCause);
}
private void addStackTraceFault() {
// check if stack trace fault is already added
Element stackElement = this.fault.lookupFaultDetail(
Constants.QNAME_FAULTDETAIL_STACKTRACE);
if (stackElement == null) {
return;
}
// remove SOAP details stack entry
this.fault.removeFaultDetail(Constants.QNAME_FAULTDETAIL_STACKTRACE);
String message = this.fault.getClass().getName();
String stackTrace = stackElement.getFirstChild().getNodeValue();
// add stack trace fault
addFaultCause(createStackFault(message, stackTrace));
}
protected void addStackTraceFault(Throwable exception) {
String message = exception.getClass().getName();
String stackTrace = JavaUtils.stackToString(exception);
// add stack trace fault
addFaultCause(createStackFault(message, stackTrace));
}
private static BaseFaultType createStackFault(String message,
String stackTrace) {
BaseFaultType stackFault = new BaseFaultType();
BaseFaultTypeErrorCode errorCode = new BaseFaultTypeErrorCode();
errorCode.setDialect(STACK_TRACE);
errorCode.set_any(AnyHelper.toText(stackTrace));
stackFault.setErrorCode(errorCode);
if (message != null && message.length() > 0) {
BaseFaultTypeDescription [] desc = new BaseFaultTypeDescription[1];
desc[0] = new BaseFaultTypeDescription(message);
stackFault.setDescription(desc);
}
stackFault.setTimestamp(Calendar.getInstance());
return stackFault;
}
/**
* Converts exception to a BaseFault.
*
* @param exception the exception to convert.
* @return If the exception is of BaseFault type then it is returned
* as is. Otherwise, the exception is converted into a BaseFault
* with the description of the exception message and with a
* chained BaseFault with {@link #STACK_TRACE STACK_TRACE}
* error code dialect and error code value that contains the
* exception stack trace.
*/
public static BaseFaultType toBaseFault(Throwable exception) {
BaseFaultType fault = null;
if (exception instanceof BaseFaultType) {
fault = (BaseFaultType)exception;
// will add the FaultCause with stack trace
FaultHelper helper = new FaultHelper(fault, false);
helper.addDescription(fault.getFaultString());
helper.addStackTraceFault();
} else {
fault = new BaseFaultType();
FaultHelper helper = new FaultHelper(fault, false);
helper.setDescription(exception.getMessage());
helper.addStackTraceFault(exception);
}
return fault;
}
/**
* Gets the error message of the exception.
*
* @param exception if exception is of type <code>BaseFaultType</code>
* {@link #getMessage() getMessage()} is
* called to get the error message. Otherwise,
* <code>getMessage</code> operation is called on the
* exception.
*/
public static String getMessage(Throwable exception) {
if (exception instanceof BaseFaultType) {
FaultHelper faultHelper =
new FaultHelper((BaseFaultType)exception, false);
return faultHelper.getMessage();
} else {
return exception.getMessage();
}
}
/**
* Gets the stack trace of the exception.
*
* @param exception if exception is of type <code>BaseFaultType</code>
* {@link #printStackTrace() printStackTrace()} is
* called to get the error message. Otherwise,
* <code>printStackTrace</code> operation is called on the
* exception.
*/
public static void printStackTrace(Throwable exception) {
if (exception instanceof BaseFaultType) {
exception.printStackTrace();
// Disabled becuase it omits client stack info
/*
FaultHelper faultHelper =
new FaultHelper((BaseFaultType)exception, false);
faultHelper.printStackTrace();
*/
} else {
exception.printStackTrace();
}
}
/**
* Prints stack trace of the fault to <code>System.err</code>.
* See {@link #getStackTrace() getStackTrace()} for more information.
*/
public void printStackTrace() {
printStackTrace(System.err);
}
/**
* Writes stack trace of the fault to stream.
* See {@link #getStackTrace() getStackTrace()} for more information.
*/
public void printStackTrace(PrintStream s) {
s.println(getStackTrace());
}
/**
* Writes stack trace of the fault to writer.
* See {@link #getStackTrace() getStackTrace()} for more information.
*/
public void printStackTrace(PrintWriter s) {
s.println(getStackTrace());
}
/**
* Gets error message of the fault.
*
* @return If the fault has error code dialect of {@link #STACK_TRACE
* STACK_TRACE} null is returned. Otherwise, the error message
* is composed of all descriptions of the fault and descriptions
* of the chained faults.
*/
public String getMessage() {
BaseFaultTypeErrorCode errorCode =
this.fault.getErrorCode();
if (errorCode != null) {
if (STACK_TRACE.equals(errorCode.getDialect())) {
return null;
}
}
StringBuffer buf = new StringBuffer();
buf.append(this.fault.getClass().getName());
String desc = getDescriptionAsString();
if (desc != null) {
buf.append(": ").append(desc);
}
BaseFaultType [] cause = this.fault.getFaultCause();
if (cause != null) {
boolean wroteCauseBy = false;
int j = 0;
for (int i=0;i<cause.length;i++) {
desc = getMessage(cause[i]);
if (desc == null) {
continue;
}
if (!wroteCauseBy) {
buf.append(i18n.getMessage("causedBy") + "[");
wroteCauseBy = true;
}
buf.append(String.valueOf(j++)).append(": ").append(desc);
}
if (wroteCauseBy) {
buf.append("]");
}
}
return buf.toString();
}
/**
* Gets stack trace of the fault. Note, this stack trace only contains
* information sent from server. It does not contain client stack
* trace information.
*
* @return stack trace of the fault. It includes any chained faults.
*/
public String getStackTrace() {
StringBuffer buf = new StringBuffer();
buf.append(this.fault.getClass().getName());
String desc = getDescriptionAsString();
if (desc != null) {
buf.append(": ").append(desc);
}
if (this.fault.getTimestamp() != null) {
buf.append(LS).append(i18n.getMessage("timestamp"));
buf.append(this.fault.getTimestamp().getTime().toString());
}
if (this.fault.getOriginator() != null) {
buf.append(LS).append(i18n.getMessage("originator"));
buf.append(this.fault.getOriginator().toString());
}
BaseFaultType [] cause = this.fault.getFaultCause();
BaseFaultTypeErrorCode errorCode = null;
if (cause != null) {
FaultHelper helper = null;
for (int i=0;i<cause.length;i++) {
helper = new FaultHelper(cause[i], false);
errorCode = cause[i].getErrorCode();
if (errorCode != null &&
STACK_TRACE.equals(errorCode.getDialect())) {
buf.append(LS);
try {
buf.append(AnyHelper.toSingleString(
errorCode.get_any()));
} catch (Exception e) {
// ?
}
continue;
}
buf.append(LS).append(i18n.getMessage("causedBy01"));
buf.append(helper.getStackTrace());
}
}
return buf.toString();
}
}