at.spardat.enterprise.exc.BaseException Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2003, 2007 s IT Solutions AT Spardat GmbH .
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* s IT Solutions AT Spardat GmbH - initial API and implementation
*******************************************************************************/
// @(#) $Id: BaseException.java 2618 2008-07-09 09:49:15Z gub $
package at.spardat.enterprise.exc;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Locale;
/**
* This class supports the notification of application or system level exceptions. Since this
* class works much the same as class {@link Notification}, please have a look there
* for the description of the API.
*
* What makes this class different from Notification is the fact that
* you can throw an instance of subclasses of BaseException. Furthermore you may
* set a detail Throwable at construction time, which should indicate
* a more detailed (often technical) reason why you are creating and throwing
* an BaseException.
*
* This class must be sublassed outside the framework.
*
* @author YSD, 21.05.2003 19:29:23
*/
public abstract class BaseException extends RuntimeException implements INotification, java.io.Serializable {
/**
* Increase this if u make non compatible changes
*/
private static final long serialVersionUID = 1L;
/**
* The wrapped notification object
*/
protected Notification notification_;
/**
* The stack trace of this
*/
private StackTrace thisStack_;
/**
* A detail exception converted to a StackTrace
*/
private StackTrace detailStack_;
/**
* The detail exception
*/
private Throwable detail_;
/**
* Indicates if this exception may be shown to the end user or not. This is just
* a hint for the presentation layer to decide if it should wrap this in
* another exception.
*/
protected boolean showToEndUser_;
/**
* For internal use only.
*/
protected BaseException () {
}
/**
* Constructs and sets the message from a format string as
* defined in java.text.MessageFormat. The required parameters
* must be contained in the params array.
*
* @param detail detail exception.
* @param message string that either is a java.text.MessageFormat
* or not, depending on params.
* @param l a java.util.Locale to format locale
* dependent data types or null if the
* params do not contain local specifics.
* @param params the parameters of the message. If not null, message
* is expected to be a string compliant to MessageFormat.
*/
public BaseException (Throwable detail, String message, Locale l, Object[] params) {
notification_ = new Notification (message, l, params);
// store the stacktrace
thisStack_ = new StackTrace(StackTrace.splitLines(getSuperStackTrace()));
// set detail
if (detail != null) setDetail (detail);
}
/**
* Returns the code. The code should be used to discrimitate among
* different kinds of exceptions/notifications.
*/
public int getCode () {
return notification_.getCode();
}
/**
* Returns true if this or any recursively nested detail BaseExceptions
* code equals code.
*
* @param code the code to be checked for equality with the code of this or
* any contained BaseException.
*/
public boolean containsCode (int code) {
if (getCode() == code) return true;
if (detail_ != null && detail_ instanceof BaseException) return ((BaseException)detail_).containsCode(code);
return false;
}
/**
* @see at.spardat.enterprise.exc.INotification#getType()
*/
public int getType() {
return notification_.getType();
}
/**
* @see at.spardat.enterprise.exc.INotification#getReaction()
*/
public int getReaction() {
return notification_.getReaction();
}
/**
* @see at.spardat.enterprise.exc.INotification#getShortMessage()
*/
public String getShortMessage() {
return notification_.getShortMessage();
}
/**
* @see at.spardat.enterprise.exc.INotification#getMessage()
*/
public String getMessage() {
return notification_.getMessage();
}
/**
* This method takes into account that BaseExceptions might be nested and
* the outermost exceptions might have empty messages. It searches
* the detail list down until it finds a non-empty message and
* returns it.
*
* @param withClassNamePraefix indicates, if the message should be praefixed
* by the classname and a colon.
*
* @return the first non-empty message in the BaseException-detail-list
* or the empty-string if all messages are empty.
*/
public String getFirstNonEmptyMessage (boolean withClassNamePraefix) {
String message = getMessage();
if (message.trim().length() > 0) return getMessage (withClassNamePraefix);
if (detail_ == null) return getMessage (withClassNamePraefix);
// recurse down if detail is BaseException
if (detail_ instanceof BaseException) return ((BaseException)detail_).getFirstNonEmptyMessage(withClassNamePraefix);
message = withClassNamePraefix ? detail_.toString() : detail_.getMessage();
return message == null ? "" : message;
}
/**
* Returns the message of this, optionally praefixed by classname and a colon.
*
* @param withClassNamePraefix indicates that a classname praefix is required.
* @return non-null String
*/
public String getMessage (boolean withClassNamePraefix) {
if (withClassNamePraefix) {
return getClass().getName() + ": " + getMessage();
} else {
return getMessage();
}
}
/**
* Sets the detail exception. This usually is the technical exception that
* is the root cause of this exception.
*
* @param detail the root cause technical exception.
*/
private void setDetail (Throwable detail) {
detail_ = detail;
detailStack_ = null;
}
/**
* Returns the detail throwable. The returned value is null if
* no detail has been set or this has been migrated and
* the detail is not an BaseException.
*
* @see #prepareMigration
*/
public Throwable getDetail () {
return detail_;
}
/**
* Initializes the cause of this throwable to the specified value.
*
* This method can be called at most once. If a detail exception has
* been passed to the constructor, it can't even be called once.
* @throws IllegalStateException if a detail exception is allread defined.
*/
public synchronized Throwable initCause(Throwable cause) {
if (detail_ != null)
throw new IllegalStateException("Can't overwrite cause");
if (cause == this)
throw new IllegalArgumentException("Self-causation not permitted");
detail_ = cause;
detailStack_ = null;
return this;
}
/**
* Returns the cause of this throwable. The returned value is null if
* no detail has been set or this has been migrated and
* the detail is not an BaseException.
* This method returns exactly the same as {@link #getDetail()}.
*
* @see #prepareMigration
*/
public Throwable getCause() {
return detail_;
}
/**
* Indicates if this exception may be directly displayed to the end user.
*/
public boolean showToEndUser () {
return showToEndUser_;
}
/**
* This method should be called if this is about to leave the JVM
* and therefore must be serialized. We expect that the recipient
* JVM is able to deserialize an BaseException, but not
* necessarely any other contained detail exception. Therefore, if the
* detail-throwable is not an BaseException, it is converted
* to a String[] and stored as String[]. {@link #getDetail} will return
* null afterwards.
*
* This method does nothing if there is no detail-throwable. If
* the detail itself is an BaseException, the method is
* called recursively.
*
* The end effect of calling this method is, to summarize the words above,
* that any contained exception, directly or indirectly, is morphed
* to a String[] if it is not an BaseException.
*/
public void prepareMigration () {
if (detail_ != null) {
if (detail_ instanceof BaseException) {
((BaseException)detail_).prepareMigration();
} else {
detailStack_ = new StackTrace(detail_);
detail_ = null;
}
}
}
/**
* Returns a String which contains the class name, message and code
* of this exception/notification.
*
* @see java.lang.Object#toString()
*/
public String toString () {
StringBuffer result = new StringBuffer();
result.append(getClass().getName());
if (notification_.getCode() != 0) {
result.append (" [");
result.append (notification_.getCode());
result.append ("]");
}
result.append(": ");
result.append(getMessage());
return result.toString();
}
/**
* Prints this to System.err.
*
* @see java.lang.Throwable#printStackTrace()
*/
public void printStackTrace() {
printStackTrace (System.err);
}
/**
* Prints this to the provided PrintStream including the stacktrace
* and the information of all contained exceptions.
*
* @see java.lang.Throwable#printStackTrace(java.io.PrintStream)
*/
public void printStackTrace (PrintStream s) {
printStackTraceRec (s, null);
}
private void printStackTraceRec (PrintStream s, StackTrace enclosing) {
synchronized (s) {
StackTrace thisStack = getStackOfThis();
thisStack.printStackTrace (s, enclosing);
if (detailStack_ != null) {
s.print ("Caused by: ");
detailStack_.printStackTrace (s, thisStack);
}
if (detail_ != null) {
s.print ("Caused by: ");
if (detail_ instanceof BaseException) ((BaseException)detail_).printStackTraceRec (s, thisStack);
else {
StackTrace detailStack = new StackTrace(detail_);
detailStack.printStackTrace (s, thisStack);
}
}
}
}
/**
* Prints this to the provided PrintWriter including the stacktrace
* and the information of all contained exceptions.
*
* @see java.lang.Throwable#printStackTrace(java.io.PrintWriter)
*/
public void printStackTrace (PrintWriter s) {
printStackTraceRec (s, null);
}
private void printStackTraceRec (PrintWriter s, StackTrace enclosing) {
synchronized (s) {
StackTrace thisStack = getStackOfThis();
thisStack.printStackTrace (s, enclosing);
if (detailStack_ != null) {
s.print ("Caused by: ");
detailStack_.printStackTrace (s, thisStack);
}
if (detail_ != null) {
s.print ("Caused by: ");
if (detail_ instanceof BaseException) ((BaseException)detail_).printStackTraceRec (s, thisStack);
else {
StackTrace detailStack = new StackTrace(detail_);
detailStack.printStackTrace (s, thisStack);
}
}
}
}
/**
* Returns the result of calling printStackTrace on super.
*/
private String getSuperStackTrace() {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
super.printStackTrace(pw);
pw.close();
return sw.toString();
}
/**
* Returns the stacktrace of this exception ignoring the detail exception.
*/
public String getOwnStackTrace() {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
StackTrace thisStack = getStackOfThis();
thisStack.printStackTrace (pw, null);
pw.close();
return sw.toString();
}
/**
* Returns the stack trace of this with a corrected header. The header
* may have changed since we have saved the stack in the constructor.
*/
public StackTrace getStackOfThis () {
if (thisStack_.isWellFormed()) {
// normally, this is well formed
String newHeader = toString();
String [] headerLines = StackTrace.splitLines(newHeader);
thisStack_.replaceHeaderLines(headerLines);
}
return thisStack_;
}
/**
* Sets the instance variables in this from ex. ex is made unusable
* with this method.
*
* Although this method is public, it may not be called from outside the framework.
*/
private void stealFrom (BaseException ex) {
notification_ = ex.notification_;
thisStack_ = ex.thisStack_;
detailStack_ = ex.detailStack_;
detail_ = ex.detail_;
showToEndUser_ = ex.showToEndUser_;
ex.notification_ = null;
ex.thisStack_ = null;
ex.detailStack_ = null;
ex.detail_ = null;
}
/**
* Creates a AppException or SysException from this that has
* the property that this and all contained BaseExceptions are instances
* of AppException or SysException, but not subclasses of them.
*
* This (the target object) is destroyed and must not be used anymore!!! This
* method must not be called from outside the framework!!!
*/
public BaseException truncateSubclasses () {
/**
* Morph this down to AppException or SysException
*/
BaseException toReturn = null;
if (this instanceof AppException) {
toReturn = new AppException("");
toReturn.stealFrom (this);
} else if (this instanceof SysException) {
toReturn = new SysException("");
toReturn.stealFrom (this);
} else {
throw new RuntimeException ("BaseException subclassing is not allowed");
}
if (toReturn.detail_ != null && toReturn.detail_ instanceof BaseException) {
/**
* Recursively on detail_
*/
toReturn.detail_ = ((BaseException)toReturn.detail_).truncateSubclasses();
}
return toReturn;
}
/**
* Sets the message of this from a one
* parameter java.text.MessageFormat compatible string.
*
* @param messageFmt format string as defined in java.text.MessageFormat.
* @param param1 message parameter
* @param param2 message parameter
* @return this
*/
public BaseException setMessage (String messageFmt, Object param1, Object param2) {
notification_.setMessage (messageFmt, param1, param2);
return this;
}
/**
* Sets the message of this from a one
* parameter java.text.MessageFormat compatible string.
*
* @param messageFmt format string as defined in java.text.MessageFormat.
* @param param1 message parameter
* @return this
*/
public BaseException setMessage (String messageFmt, Object param1) {
notification_.setMessage (messageFmt, param1);
return this;
}
/**
* Constructs and sets the message to the provided value.
*
* @param message text to set
* @return this
*/
public BaseException setMessage (String message) {
notification_.setMessage (message);
return this;
}
}