org.beangle.security.web.util.ThrowableAnalyzer Maven / Gradle / Ivy
/*
* Beangle, Agile Java/Scala Development Scaffold and Toolkit
*
* Copyright (c) 2005-2012, Beangle Software.
*
* Beangle is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Beangle is distributed in the hope that it will be useful.
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Beangle. If not, see .
*/
package org.beangle.security.web.util;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
* Handler for analyzing {@link Throwable} instances. Can be subclassed to
* customize its behavior.
*
* @author chaostone
* @since 2.0
* @version $Id: ThrowableAnalyzer.java 2559 2008-01-30 16:15:02Z $
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public class ThrowableAnalyzer {
/**
* Default extractor for {@link Throwable} instances.
*
* @see Throwable#getCause()
*/
public static final ThrowableCauseExtractor DEFAULT_EXTRACTOR = new ThrowableCauseExtractor() {
public Throwable extractCause(Throwable throwable) {
return throwable.getCause();
}
};
/**
* Default extractor for {@link InvocationTargetException} instances.
*
* @see InvocationTargetException#getTargetException()
*/
public static final ThrowableCauseExtractor INVOCATIONTARGET_EXTRACTOR = new ThrowableCauseExtractor() {
public Throwable extractCause(Throwable throwable) {
verifyThrowableHierarchy(throwable, InvocationTargetException.class);
return ((InvocationTargetException) throwable).getTargetException();
}
};
/**
* Comparator to order classes ascending according to their hierarchy
* relation. If two classes have a hierarchical relation, the "higher" class
* is considered to be greater by this comparator.
* For hierarchically unrelated classes their fully qualified name will be
* compared.
*/
private static final Comparator CLASS_HIERARCHY_COMPARATOR = new Comparator() {
public int compare(Object o1, Object o2) {
Class class1 = (Class) o1;
Class class2 = (Class) o2;
if (class1.isAssignableFrom(class2)) {
return 1;
} else if (class2.isAssignableFrom(class1)) {
return -1;
} else {
return class1.getName().compareTo(class2.getName());
}
}
};
/**
* Map of registered cause extractors. key: Class; value:
* ThrowableCauseExctractor
*/
private final Map extractorMap;
/**
* Creates a new ThrowableAnalyzer
instance.
*/
public ThrowableAnalyzer() {
this.extractorMap = new TreeMap(CLASS_HIERARCHY_COMPARATOR);
initExtractorMap();
}
/**
* Registers a ThrowableCauseExtractor
for the specified type.
* Can be used in subclasses overriding {@link #initExtractorMap()}.
*
* @param throwableType
* the type (has to be a subclass of Throwable
)
* @param extractor
* the associated ThrowableCauseExtractor
(not null
)
* @throws IllegalArgumentException
* if one of the arguments is invalid
*/
protected final void registerExtractor(Class throwableType, ThrowableCauseExtractor extractor) {
verifyThrowableType(throwableType);
if (extractor == null) { throw new IllegalArgumentException("Invalid extractor: null"); }
this.extractorMap.put(throwableType, extractor);
}
/**
* Initializes associations between Throwable
s and
* ThrowableCauseExtractor
s. The default implementation
* performs the following registrations: {@link #DEFAULT_EXTRACTOR} for {@link Throwable}
* {@link #INVOCATIONTARGET_EXTRACTOR} for {@link InvocationTargetException}
* Subclasses overriding this method are encouraged to invoke the super
* method to perform the default registrations. They can register additional
* extractors as required.
*
* Note: An extractor registered for a specific type is applicable for that type and all
* subtypes thereof. However, extractors registered to more specific types are guaranteed to
* be resolved first. So in the default case InvocationTargetExceptions will be handled by
* {@link #INVOCATIONTARGET_EXTRACTOR} while all other throwables are handled by
* {@link #DEFAULT_EXTRACTOR}.
*
* @see #registerExtractor(Class, ThrowableCauseExtractor)
*/
protected void initExtractorMap() {
registerExtractor(InvocationTargetException.class, INVOCATIONTARGET_EXTRACTOR);
registerExtractor(Throwable.class, DEFAULT_EXTRACTOR);
}
/**
* Returns an array containing the classes for which extractors are
* registered. The order of the classes is the order in which comparisons
* will occur for resolving a matching extractor.
*
* @return the types for which extractors are registered
*/
final Class[] getRegisteredTypes() {
List typeList = new ArrayList(this.extractorMap.keySet());
return (Class[]) typeList.toArray(new Class[typeList.size()]);
}
/**
* Determines the cause chain of the provided Throwable
. The
* returned array contains all throwables extracted from the stacktrace,
* using the registered {@link ThrowableCauseExtractor extractors}. The
* elements of the array are ordered: The first element is the passed in
* throwable itself. The following elements appear in their order downward
* the stacktrace.
*
* Note: If no {@link ThrowableCauseExtractor} is registered for this instance then the returned
* array will always only contain the passed in throwable.
*
* @param throwable
* the Throwable
to analyze
* @return an array of all determined throwables from the stacktrace
* @throws IllegalArgumentException
* if the throwable is null
* @see #initExtractorMap()
*/
public final Throwable[] determineCauseChain(Throwable throwable) {
if (throwable == null) { throw new IllegalArgumentException("Invalid throwable: null"); }
List chain = new ArrayList();
Throwable currentThrowable = throwable;
while (currentThrowable != null) {
chain.add(currentThrowable);
currentThrowable = extractCause(currentThrowable);
}
return (Throwable[]) chain.toArray(new Throwable[chain.size()]);
}
/**
* Extracts the cause of the given throwable using an appropriate extractor.
*
* @param throwable
* the Throwable
(not null
* @return the cause, may be null
if none could be resolved
*/
private Throwable extractCause(Throwable throwable) {
for (Iterator iter = this.extractorMap.entrySet().iterator(); iter.hasNext();) {
Map.Entry entry = (Map.Entry) iter.next();
Class throwableType = (Class) entry.getKey();
if (throwableType.isInstance(throwable)) {
ThrowableCauseExtractor extractor = (ThrowableCauseExtractor) entry.getValue();
return extractor.extractCause(throwable);
}
}
return null;
}
/**
* Returns the first throwable from the passed in array that is assignable
* to the provided type. A returned instance is safe to be cast to the
* specified type.
*
* If the passed in array is null or empty this method returns null
.
*
* @param throwableType
* the type to look for
* @param chain
* the array (will be processed in element order)
* @return the found Throwable
, null
if not found
* @throws IllegalArgumentException
* if the provided type is null
or no subclass of Throwable
*/
public final Throwable getFirstThrowableOfType(Class throwableType, Throwable[] chain) {
verifyThrowableType(throwableType);
if (chain != null) {
for (int i = 0; i < chain.length; ++i) {
Throwable t = chain[i];
if ((t != null) && throwableType.isInstance(t)) { return t; }
}
}
return null;
}
/**
* Convenience method for verifying that the passed in class refers to a
* valid subclass of Throwable
.
*
* @param throwableType
* the type to check
* @throws IllegalArgumentException
* if typeToCheck
is either null
or
* not assignable to expectedBaseType
*/
private static void verifyThrowableType(Class throwableType) {
if (throwableType == null) { throw new IllegalArgumentException("Invalid type: null"); }
if (!Throwable.class.isAssignableFrom(throwableType)) { throw new IllegalArgumentException(
"Invalid type: '" + throwableType.getName() + "'. Has to be a subclass of '"
+ Throwable.class.getName() + "'"); }
}
/**
* Verifies that the provided throwable is a valid subclass of the provided
* type (or of the type itself). If expectdBaseType
is null
, no check
* will be performed.
*
* Can be used for verification purposes in implementations of {@link ThrowableCauseExtractor
* extractors}.
*
* @param throwable
* the Throwable
to check
* @param expectedBaseType
* the type to check against
* @throws IllegalArgumentException
* if throwable
is either null
or its
* type is not assignable to expectedBaseType
*/
public static final void verifyThrowableHierarchy(Throwable throwable, Class expectedBaseType) {
if (expectedBaseType == null) { return; }
if (throwable == null) { throw new IllegalArgumentException("Invalid throwable: null"); }
Class throwableType = throwable.getClass();
if (!expectedBaseType.isAssignableFrom(throwableType)) { throw new IllegalArgumentException(
"Invalid type: '" + throwableType.getName() + "'. Has to be a subclass of '"
+ expectedBaseType.getName() + "'"); }
}
}