//
// Copyright (c)1998-2011 Pearson Education, Inc. or its affiliate(s).
// All rights reserved.
//
package openadk.library;
import java.util.Vector;
import java.io.*;
import openadk.library.Zone;
import openadk.library.impl.ZoneImpl;
import org.apache.log4j.Category;
/**
* The base class for all exception classes defined by the ADK.<p>
*
* ADKExceptions have the following characteristics:<p>
*
* <ul>
* <li>
* An exception may contain one or more child exceptions. All ADK
* methods that operate on multiple zones or topics take a fail-late
* approach where the method continues processing even when an error
* occurs. Only after all zones and topics have been enumerated is a
* final exception thrown. It may contain multiple child exceptions
* collected during the processing.
* </li>
* <li>
* The caller can obtain a reference to the Zone in which the exception
* occurred.
* </li>
* </ul>
* <p>
*
* @author Eric Petersen
* @version ADK 1.0
*/
public class ADKException extends Exception implements Serializable
{
/**
*
*/
private static final long serialVersionUID = Element.CURRENT_SERIALIZE_VERSION;
/**
* When this flag is enabled, the ADK will retain the message associated
* with the exception rather than removing it from the agent's queue, if
* possible.
* @see #setRetry(boolean)
*/
private static final int FLG_RETRY = 0x00000001;
/**
* The Zone associated with this exception. Because the Zone interface does
* not support RMI, this is a transient data member. Rather than calling
* <code>getZone</code> to retrieve the zone associated with an ADKException,
* RMI-based clients should use the <code>getZoneId</code> method
* instead.
*/
private transient Zone fZone;
/**
* The ID of the zone associated with this exception
*/
private String fZoneId;
/**
* The nested child exceptions
*/
private Vector<Throwable> fChildren;
/**
* Optional exception flags that may influence how the ADK handles this
* exception when it is caught within the class framework
*/
private int fFlags;
/**
* Constructs an exception with a detailed message that occurs in the
* context of a zone<p>
* @param msg A message describing the exception
* @param zone The zone associated with the exception
*/
public ADKException( String msg, Zone zone )
{
super(msg);
fZone = zone;
fZoneId = zone == null ? null : zone.getZoneId();
}
/**
* Constructs an exception with a detailed message that occurs in the
* context of a zone<p>
* @param msg A message describing the exception
* @param zone The zone associated with the exception
* @param src The source exception
*/
public ADKException( String msg, Zone zone, Throwable src )
{
super(msg, src);
fZone = zone;
fZoneId = zone == null ? null : zone.getZoneId();
}
/**
* Gets the zone associated with this exception.<p>
*
* <b>Note:</b> The ADK's <i>Zone</i> interface does not support Java RMI.
* Therefore, this method will return a <code>null</code> value when called
* by an RMI-based client on a marshalled ADKException object. RMI clients
* should instead use the <code>getZoneId</code> method to learn the ID of
* the zone passed to the constructor.
* <p>
*
* @return The Zone associated with the exception
*
* @see #getZoneId
*/
public Zone getZone()
{
return fZone;
}
/**
* Gets the ID of the zone associated with this exception.<p>
* @return the ID of the Zone passed to the constructor
* @see #getZone
*/
public String getZoneId()
{
return fZoneId;
}
/**
* Determines if this exception has nested child exceptions
* @return true if this exception has nested child exceptions
*/
public boolean hasChildren() {
return fChildren != null;
}
/**
* Adds a child exception
* @param thr A new child exception
*/
public void add( Throwable thr ) {
if( fChildren == null )
fChildren = new Vector<Throwable>();
fChildren.addElement(thr);
}
/**
* Gets the child exceptions, if any
* @return All child excpeptions. If no child exceptions are defined,
* an emtpy array is returned
*/
public Throwable[] getChildren() {
Throwable[] arr = new Throwable[ fChildren == null ? 0 : fChildren.size() ];
if( fChildren != null )
fChildren.copyInto(arr);
return arr;
}
/**
* Determines if the ADK should attempt to retry the operation associated
* with this exception.
* @param retry When true, the exception is flagged for retry. The method
* that catches the exception should attempt to retry the operation in
* progress.
*/
public void setRetry( boolean retry ) {
if( retry ) {
fFlags |= FLG_RETRY;
} else {
fFlags &= ~FLG_RETRY;
}
}
/**
* Determines if the ADK should attempt to retry the operation associated
* with this exception.
* @return True if the exception is flagged for retry (the default is
* false). The method that catches the exception should attempt to
* retry the operation in progress.
*/
public boolean getRetry() {
return ( fFlags & FLG_RETRY ) != 0 ;
}
/**
* Determines if this exception contains any SIFException children.
* SIFException encapsulates SIF errors returned to the agent in SIF_Ack
* messages.
*
* @return true if this exception has at least one nested SIFException
*/
public boolean hasSIFExceptions()
{
if( fChildren != null ) {
for( int i = 0; i < fChildren.size(); i++ ) {
if( fChildren.elementAt(i) instanceof SIFException )
return true;
}
}
return false;
}
/**
* Gets all child SIFExceptions, if any
* @return An array of SIFExceptions
*/
public SIFException[] getSIFExceptions()
{
Vector<Throwable> v = new Vector<Throwable>();
if( fChildren != null ) {
for( Throwable t : v ) {
if( t instanceof SIFException )
v.addElement( t );
}
}
SIFException[] arr = new SIFException[ v.size() ];
v.copyInto(arr);
return arr;
}
/**
* Determines if this exception contains at least one nested SIFException
* with the specified error category.<p>
* @deprecated Please use the overload of this method that accepts a SIFErrorCategory
* @param category The error category to search for
* @return true if this exception has nested SIFExceptions and at least one
* of those has the specified error category.
*/
public boolean hasSIFError( int category )
{
if( this instanceof SIFException )
return ((SIFException)this).hasErrorCategory( category );
return _recurseError( this, SIFErrorCategory.lookup( category ) );
}
/**
* Determines if this exception contains at least one nested SIFException
* with the specified error category.<p>
* @param category The error category to search for
* @return true if this exception has nested SIFExceptions and at least one
* of those has the specified error category.
*/
public boolean hasSIFError( SIFErrorCategory category )
{
if( this instanceof SIFException )
return ((SIFException)this).hasErrorCategory( category );
return _recurseError( this, category );
}
/**
* Determines if this exception contains at least one nested SIFException
* with the specified error category and code.<p>
* @deprecated Please use the overload of this method that accepts a SIFErrorCategory
* as the first parameter
* @param category The error category to search for
* @param code The error code to search for
* @return true if this exception has nested SIFExceptions and at least one
* of those has the specified error category and code
*/
public boolean hasSIFError( int category, int code )
{
if( this instanceof SIFException ){
return ((SIFException)this).hasError( category, code );
}
return _recurseError( this, SIFErrorCategory.lookup( category ), code );
}
/**
* Determines if this exception contains at least one nested SIFException
* with the specified error category and code.<p>
* @param category The error category to search for
* @param code The error code to search for
* @return true if this exception has nested SIFExceptions and at least one
* of those has the specified error category and code
*/
public boolean hasSIFError( SIFErrorCategory category, int code )
{
if( this instanceof SIFException )
return ((SIFException)this).hasError( category,code );
return _recurseError( this, category, code );
}
private boolean _recurseError( ADKException parent, SIFErrorCategory category, int code )
{
if( parent.fChildren != null )
{
Throwable ch = null;
for( int i = 0; i < fChildren.size(); i++ )
{
ch = fChildren.elementAt(i);
if( ch instanceof SIFException ) {
if( ((SIFException)ch).hasError( category, code ) )
return true;
}
else
if( ch instanceof ADKException ) {
if( _recurseError( (ADKException)ch, category, code ) )
return true;
}
}
}
return false;
}
private boolean _recurseError( ADKException parent, SIFErrorCategory category )
{
if( parent.fChildren != null )
{
Throwable ch = null;
for( int i = 0; i < fChildren.size(); i++ )
{
ch = fChildren.elementAt(i);
if( ch instanceof SIFException ) {
if( ((SIFException)ch).hasErrorCategory( category ) )
return true;
}
else
if( ch instanceof ADKException ) {
if( _recurseError( (ADKException)ch, category ) )
return true;
}
}
}
return false;
}
/**
* Returns a string representation of this exception and all child
* exceptions formatted for printing to System.out. Each exception is
* on its own line.
* @return A string representation of this exception and all child exceptions
*/
public String toString()
{
return toString(0,true);
}
/**
* Returns a string representation of this exception and all child
* exceptions formatted for printing to System.out. Each exception is
* on its own line.
* @param indent The amount of indentation to apply to the string
* @param includeChildren True if all children should be returned
* @return A string representation of this exception
*/
public String toString( int indent, boolean includeChildren )
{
StringBuffer b = new StringBuffer();
for( int i = 0; i < indent; i++ )
b.append(" ");
// Print detailed message if any
if( indent != 0 )
b.append( "Zone " + ( fZone == null ? "(Unknown)" : fZone.getZoneId() ) + ": ");
b.append( getMessage() );
// Print nested exceptions if any
if( fChildren != null && includeChildren )
{
b.append(":");
b.append("\r\n");
int cnt = fChildren.size();
for( int i = 0; i < cnt; i++ )
{
Throwable innerX = fChildren.elementAt(i);
if( innerX instanceof ADKException )
b.append( ((ADKException)innerX).toString(indent+1,true) );
else
{
StringBuffer msg = new StringBuffer();
for( int k = 0; k < (indent+1); k++ )
msg.append(" ");
msg.append(innerX.toString());
b.append(msg.toString());
}
b.append("\r\n");
}
}
return b.toString();
}
/**
* Write this exception and all of its nested exceptions to Log4j. For
* any exception that is not associated with a zone, the supplied default
* Category will be used. Otherwise the Category of the zone is used.
* @param def The category to log to
*/
public void log( Category def )
{
log(def,0);
}
/**
* Logs the exception to the Category, using the "error" logging level
* @param def The Category to log to
* @param indent The amount of indentation to apply
*/
public void log( Category def, int indent )
{
Category target = fZone == null ? def : ((ZoneImpl)fZone).log;
if( target == null ){
return;
}
target.error(toString(indent,false), this);
if( fChildren != null )
{
int cnt = fChildren.size();
for( int i = 0; i < cnt; i++ )
{
Throwable innerX = fChildren.elementAt(i);
if( innerX instanceof ADKException )
((ADKException)innerX).log(def,indent+1);
else
{
StringBuffer msg = new StringBuffer();
for( int k = 0; k < (indent+1); k++ )
msg.append(" ");
msg.append( innerX.toString() );
target.error( msg.toString(), this );
}
}
}
}
}