|
@@ -3,16 +3,18 @@ package org.springframework.security.util;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.util.ArrayList;
|
|
import java.util.ArrayList;
|
|
import java.util.Comparator;
|
|
import java.util.Comparator;
|
|
-import java.util.Iterator;
|
|
|
|
import java.util.List;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
|
|
+import java.util.Set;
|
|
import java.util.TreeMap;
|
|
import java.util.TreeMap;
|
|
|
|
|
|
|
|
+import org.springframework.util.Assert;
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* Handler for analyzing {@link Throwable} instances.
|
|
* Handler for analyzing {@link Throwable} instances.
|
|
*
|
|
*
|
|
* Can be subclassed to customize its behavior.
|
|
* Can be subclassed to customize its behavior.
|
|
- *
|
|
|
|
|
|
+ *
|
|
* @author Andreas Senft
|
|
* @author Andreas Senft
|
|
* @since 2.0
|
|
* @since 2.0
|
|
* @version $Id$
|
|
* @version $Id$
|
|
@@ -21,7 +23,7 @@ public class ThrowableAnalyzer {
|
|
|
|
|
|
/**
|
|
/**
|
|
* Default extractor for {@link Throwable} instances.
|
|
* Default extractor for {@link Throwable} instances.
|
|
- *
|
|
|
|
|
|
+ *
|
|
* @see Throwable#getCause()
|
|
* @see Throwable#getCause()
|
|
*/
|
|
*/
|
|
public static final ThrowableCauseExtractor DEFAULT_EXTRACTOR
|
|
public static final ThrowableCauseExtractor DEFAULT_EXTRACTOR
|
|
@@ -30,13 +32,13 @@ public class ThrowableAnalyzer {
|
|
return throwable.getCause();
|
|
return throwable.getCause();
|
|
}
|
|
}
|
|
};
|
|
};
|
|
-
|
|
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* Default extractor for {@link InvocationTargetException} instances.
|
|
* Default extractor for {@link InvocationTargetException} instances.
|
|
- *
|
|
|
|
|
|
+ *
|
|
* @see InvocationTargetException#getTargetException()
|
|
* @see InvocationTargetException#getTargetException()
|
|
*/
|
|
*/
|
|
- public static final ThrowableCauseExtractor INVOCATIONTARGET_EXTRACTOR
|
|
|
|
|
|
+ public static final ThrowableCauseExtractor INVOCATIONTARGET_EXTRACTOR
|
|
= new ThrowableCauseExtractor() {
|
|
= new ThrowableCauseExtractor() {
|
|
public Throwable extractCause(Throwable throwable) {
|
|
public Throwable extractCause(Throwable throwable) {
|
|
verifyThrowableHierarchy(throwable, InvocationTargetException.class);
|
|
verifyThrowableHierarchy(throwable, InvocationTargetException.class);
|
|
@@ -46,16 +48,14 @@ public class ThrowableAnalyzer {
|
|
|
|
|
|
/**
|
|
/**
|
|
* Comparator to order classes ascending according to their hierarchy relation.
|
|
* Comparator to order classes ascending according to their hierarchy relation.
|
|
- * If two classes have a hierarchical relation, the "higher" class is considered
|
|
|
|
|
|
+ * If two classes have a hierarchical relation, the "higher" class is considered
|
|
* to be greater by this comparator.<br>
|
|
* to be greater by this comparator.<br>
|
|
- * For hierarchically unrelated classes their fully qualified name will be compared.
|
|
|
|
|
|
+ * For hierarchically unrelated classes their fully qualified name will be compared.
|
|
*/
|
|
*/
|
|
- private static final Comparator CLASS_HIERARCHY_COMPARATOR = new Comparator() {
|
|
|
|
|
|
+ private static final Comparator<Class<? extends Throwable>> CLASS_HIERARCHY_COMPARATOR =
|
|
|
|
+ new Comparator<Class<? extends Throwable>>() {
|
|
|
|
|
|
- public int compare(Object o1, Object o2) {
|
|
|
|
- Class class1 = (Class) o1;
|
|
|
|
- Class class2 = (Class) o2;
|
|
|
|
-
|
|
|
|
|
|
+ public int compare(Class<? extends Throwable> class1, Class<? extends Throwable> class2) {
|
|
if (class1.isAssignableFrom(class2)) {
|
|
if (class1.isAssignableFrom(class2)) {
|
|
return 1;
|
|
return 1;
|
|
} else if (class2.isAssignableFrom(class1)) {
|
|
} else if (class2.isAssignableFrom(class1)) {
|
|
@@ -64,41 +64,37 @@ public class ThrowableAnalyzer {
|
|
return class1.getName().compareTo(class2.getName());
|
|
return class1.getName().compareTo(class2.getName());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
};
|
|
};
|
|
-
|
|
|
|
|
|
+
|
|
|
|
|
|
/**
|
|
/**
|
|
* Map of registered cause extractors.
|
|
* Map of registered cause extractors.
|
|
* key: Class<Throwable>; value: ThrowableCauseExctractor
|
|
* key: Class<Throwable>; value: ThrowableCauseExctractor
|
|
*/
|
|
*/
|
|
- private final Map extractorMap;
|
|
|
|
-
|
|
|
|
-
|
|
|
|
|
|
+ private final Map<Class<? extends Throwable>, ThrowableCauseExtractor> extractorMap;
|
|
|
|
+
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* Creates a new <code>ThrowableAnalyzer</code> instance.
|
|
* Creates a new <code>ThrowableAnalyzer</code> instance.
|
|
*/
|
|
*/
|
|
public ThrowableAnalyzer() {
|
|
public ThrowableAnalyzer() {
|
|
- this.extractorMap = new TreeMap(CLASS_HIERARCHY_COMPARATOR);
|
|
|
|
-
|
|
|
|
|
|
+ this.extractorMap = new TreeMap<Class<? extends Throwable>, ThrowableCauseExtractor>(CLASS_HIERARCHY_COMPARATOR);
|
|
|
|
+
|
|
initExtractorMap();
|
|
initExtractorMap();
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* Registers a <code>ThrowableCauseExtractor</code> for the specified type.
|
|
* Registers a <code>ThrowableCauseExtractor</code> for the specified type.
|
|
* <i>Can be used in subclasses overriding {@link #initExtractorMap()}.</i>
|
|
* <i>Can be used in subclasses overriding {@link #initExtractorMap()}.</i>
|
|
- *
|
|
|
|
|
|
+ *
|
|
* @param throwableType the type (has to be a subclass of <code>Throwable</code>)
|
|
* @param throwableType the type (has to be a subclass of <code>Throwable</code>)
|
|
* @param extractor the associated <code>ThrowableCauseExtractor</code> (not <code>null</code>)
|
|
* @param extractor the associated <code>ThrowableCauseExtractor</code> (not <code>null</code>)
|
|
- *
|
|
|
|
|
|
+ *
|
|
* @throws IllegalArgumentException if one of the arguments is invalid
|
|
* @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");
|
|
|
|
- }
|
|
|
|
|
|
+ protected final void registerExtractor(Class<? extends Throwable> throwableType, ThrowableCauseExtractor extractor) {
|
|
|
|
+ Assert.notNull(extractor, "Invalid extractor: null");
|
|
|
|
|
|
this.extractorMap.put(throwableType, extractor);
|
|
this.extractorMap.put(throwableType, extractor);
|
|
}
|
|
}
|
|
@@ -116,26 +112,26 @@ public class ThrowableAnalyzer {
|
|
* However, extractors registered to more specific types are guaranteed to be resolved first.
|
|
* 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}
|
|
* So in the default case InvocationTargetExceptions will be handled by {@link #INVOCATIONTARGET_EXTRACTOR}
|
|
* while all other throwables are handled by {@link #DEFAULT_EXTRACTOR}.
|
|
* while all other throwables are handled by {@link #DEFAULT_EXTRACTOR}.
|
|
- *
|
|
|
|
|
|
+ *
|
|
* @see #registerExtractor(Class, ThrowableCauseExtractor)
|
|
* @see #registerExtractor(Class, ThrowableCauseExtractor)
|
|
*/
|
|
*/
|
|
protected void initExtractorMap() {
|
|
protected void initExtractorMap() {
|
|
registerExtractor(InvocationTargetException.class, INVOCATIONTARGET_EXTRACTOR);
|
|
registerExtractor(InvocationTargetException.class, INVOCATIONTARGET_EXTRACTOR);
|
|
registerExtractor(Throwable.class, DEFAULT_EXTRACTOR);
|
|
registerExtractor(Throwable.class, DEFAULT_EXTRACTOR);
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* Returns an array containing the classes for which extractors are registered.
|
|
* 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
|
|
* The order of the classes is the order in which comparisons will occur for
|
|
* resolving a matching extractor.
|
|
* resolving a matching extractor.
|
|
- *
|
|
|
|
|
|
+ *
|
|
* @return the types for which extractors are registered
|
|
* @return the types for which extractors are registered
|
|
*/
|
|
*/
|
|
final Class[] getRegisteredTypes() {
|
|
final Class[] getRegisteredTypes() {
|
|
- List typeList = new ArrayList(this.extractorMap.keySet());
|
|
|
|
- return (Class[]) typeList.toArray(new Class[typeList.size()]);
|
|
|
|
|
|
+ Set<Class<? extends Throwable>> typeList = this.extractorMap.keySet();
|
|
|
|
+ return typeList.toArray(new Class[typeList.size()]);
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* Determines the cause chain of the provided <code>Throwable</code>.
|
|
* Determines the cause chain of the provided <code>Throwable</code>.
|
|
* The returned array contains all throwables extracted from the stacktrace, using the registered
|
|
* The returned array contains all throwables extracted from the stacktrace, using the registered
|
|
@@ -143,127 +139,103 @@ public class ThrowableAnalyzer {
|
|
* The first element is the passed in throwable itself. The following elements
|
|
* The first element is the passed in throwable itself. The following elements
|
|
* appear in their order downward the stacktrace.
|
|
* appear in their order downward the stacktrace.
|
|
* <p>
|
|
* <p>
|
|
- * Note: If no {@link ThrowableCauseExtractor} is registered for this instance
|
|
|
|
|
|
+ * Note: If no {@link ThrowableCauseExtractor} is registered for this instance
|
|
* then the returned array will always only contain the passed in throwable.
|
|
* then the returned array will always only contain the passed in throwable.
|
|
- *
|
|
|
|
|
|
+ *
|
|
* @param throwable the <code>Throwable</code> to analyze
|
|
* @param throwable the <code>Throwable</code> to analyze
|
|
* @return an array of all determined throwables from the stacktrace
|
|
* @return an array of all determined throwables from the stacktrace
|
|
- *
|
|
|
|
|
|
+ *
|
|
* @throws IllegalArgumentException if the throwable is <code>null</code>
|
|
* @throws IllegalArgumentException if the throwable is <code>null</code>
|
|
- *
|
|
|
|
|
|
+ *
|
|
* @see #initExtractorMap()
|
|
* @see #initExtractorMap()
|
|
*/
|
|
*/
|
|
public final Throwable[] determineCauseChain(Throwable throwable) {
|
|
public final Throwable[] determineCauseChain(Throwable throwable) {
|
|
if (throwable == null) {
|
|
if (throwable == null) {
|
|
throw new IllegalArgumentException("Invalid throwable: null");
|
|
throw new IllegalArgumentException("Invalid throwable: null");
|
|
}
|
|
}
|
|
-
|
|
|
|
- List chain = new ArrayList();
|
|
|
|
|
|
+
|
|
|
|
+ List<Throwable> chain = new ArrayList<Throwable>();
|
|
Throwable currentThrowable = throwable;
|
|
Throwable currentThrowable = throwable;
|
|
-
|
|
|
|
|
|
+
|
|
while (currentThrowable != null) {
|
|
while (currentThrowable != null) {
|
|
chain.add(currentThrowable);
|
|
chain.add(currentThrowable);
|
|
currentThrowable = extractCause(currentThrowable);
|
|
currentThrowable = extractCause(currentThrowable);
|
|
}
|
|
}
|
|
-
|
|
|
|
- return (Throwable[]) chain.toArray(new Throwable[chain.size()]);
|
|
|
|
|
|
+
|
|
|
|
+ return chain.toArray(new Throwable[chain.size()]);
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* Extracts the cause of the given throwable using an appropriate extractor.
|
|
* Extracts the cause of the given throwable using an appropriate extractor.
|
|
- *
|
|
|
|
|
|
+ *
|
|
* @param throwable the <code>Throwable</code> (not <code>null</code>
|
|
* @param throwable the <code>Throwable</code> (not <code>null</code>
|
|
* @return the cause, may be <code>null</code> if none could be resolved
|
|
* @return the cause, may be <code>null</code> if none could be resolved
|
|
*/
|
|
*/
|
|
private Throwable extractCause(Throwable throwable) {
|
|
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();
|
|
|
|
|
|
+ for (Map.Entry<Class<? extends Throwable>, ThrowableCauseExtractor> entry : extractorMap.entrySet()) {
|
|
|
|
+ Class<? extends Throwable> throwableType = entry.getKey();
|
|
if (throwableType.isInstance(throwable)) {
|
|
if (throwableType.isInstance(throwable)) {
|
|
ThrowableCauseExtractor extractor = (ThrowableCauseExtractor) entry.getValue();
|
|
ThrowableCauseExtractor extractor = (ThrowableCauseExtractor) entry.getValue();
|
|
return extractor.extractCause(throwable);
|
|
return extractor.extractCause(throwable);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
return null;
|
|
return null;
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* Returns the first throwable from the passed in array that is assignable to the provided type.
|
|
* 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.
|
|
* A returned instance is safe to be cast to the specified type.
|
|
* <p>
|
|
* <p>
|
|
* If the passed in array is null or empty this method returns <code>null</code>.
|
|
* If the passed in array is null or empty this method returns <code>null</code>.
|
|
- *
|
|
|
|
|
|
+ *
|
|
* @param throwableType the type to look for
|
|
* @param throwableType the type to look for
|
|
* @param chain the array (will be processed in element order)
|
|
* @param chain the array (will be processed in element order)
|
|
* @return the found <code>Throwable</code>, <code>null</code> if not found
|
|
* @return the found <code>Throwable</code>, <code>null</code> if not found
|
|
- *
|
|
|
|
- * @throws IllegalArgumentException if the provided type is <code>null</code>
|
|
|
|
|
|
+ *
|
|
|
|
+ * @throws IllegalArgumentException if the provided type is <code>null</code>
|
|
* or no subclass of <code>Throwable</code>
|
|
* or no subclass of <code>Throwable</code>
|
|
*/
|
|
*/
|
|
- public final Throwable getFirstThrowableOfType(Class throwableType, Throwable[] chain) {
|
|
|
|
- verifyThrowableType(throwableType);
|
|
|
|
-
|
|
|
|
|
|
+ public final Throwable getFirstThrowableOfType(Class<? extends Throwable> throwableType, Throwable[] chain) {
|
|
if (chain != null) {
|
|
if (chain != null) {
|
|
for (int i = 0; i < chain.length; ++i) {
|
|
for (int i = 0; i < chain.length; ++i) {
|
|
Throwable t = chain[i];
|
|
Throwable t = chain[i];
|
|
-
|
|
|
|
|
|
+
|
|
if ((t != null) && throwableType.isInstance(t)) {
|
|
if ((t != null) && throwableType.isInstance(t)) {
|
|
return t;
|
|
return t;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
return null;
|
|
return null;
|
|
}
|
|
}
|
|
-
|
|
|
|
- /**
|
|
|
|
- * Convenience method for verifying that the passed in class refers to a valid
|
|
|
|
- * subclass of <code>Throwable</code>.
|
|
|
|
- *
|
|
|
|
- * @param throwableType the type to check
|
|
|
|
- *
|
|
|
|
- * @throws IllegalArgumentException if <code>typeToCheck</code> is either <code>null</code>
|
|
|
|
- * or not assignable to <code>expectedBaseType</code>
|
|
|
|
- */
|
|
|
|
- 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).
|
|
* Verifies that the provided throwable is a valid subclass of the provided type (or of the type itself).
|
|
* If <code>expectdBaseType</code> is <code>null</code>, no check will be performed.
|
|
* If <code>expectdBaseType</code> is <code>null</code>, no check will be performed.
|
|
* <p>
|
|
* <p>
|
|
- * Can be used for verification purposes in implementations
|
|
|
|
|
|
+ * Can be used for verification purposes in implementations
|
|
* of {@link ThrowableCauseExtractor extractors}.
|
|
* of {@link ThrowableCauseExtractor extractors}.
|
|
- *
|
|
|
|
|
|
+ *
|
|
* @param throwable the <code>Throwable</code> to check
|
|
* @param throwable the <code>Throwable</code> to check
|
|
* @param expectedBaseType the type to check against
|
|
* @param expectedBaseType the type to check against
|
|
- *
|
|
|
|
|
|
+ *
|
|
* @throws IllegalArgumentException if <code>throwable</code> is either <code>null</code>
|
|
* @throws IllegalArgumentException if <code>throwable</code> is either <code>null</code>
|
|
* or its type is not assignable to <code>expectedBaseType</code>
|
|
* or its type is not assignable to <code>expectedBaseType</code>
|
|
*/
|
|
*/
|
|
- public static final void verifyThrowableHierarchy(Throwable throwable, Class expectedBaseType) {
|
|
|
|
|
|
+ public static final void verifyThrowableHierarchy(Throwable throwable, Class<? extends Throwable> expectedBaseType) {
|
|
if (expectedBaseType == null) {
|
|
if (expectedBaseType == null) {
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
if (throwable == null) {
|
|
if (throwable == null) {
|
|
throw new IllegalArgumentException("Invalid throwable: null");
|
|
throw new IllegalArgumentException("Invalid throwable: null");
|
|
}
|
|
}
|
|
- Class throwableType = throwable.getClass();
|
|
|
|
-
|
|
|
|
|
|
+ Class<? extends Throwable> throwableType = throwable.getClass();
|
|
|
|
+
|
|
if (!expectedBaseType.isAssignableFrom(throwableType)) {
|
|
if (!expectedBaseType.isAssignableFrom(throwableType)) {
|
|
- throw new IllegalArgumentException("Invalid type: '"
|
|
|
|
- + throwableType.getName()
|
|
|
|
|
|
+ throw new IllegalArgumentException("Invalid type: '"
|
|
|
|
+ + throwableType.getName()
|
|
+ "'. Has to be a subclass of '" + expectedBaseType.getName() + "'");
|
|
+ "'. Has to be a subclass of '" + expectedBaseType.getName() + "'");
|
|
}
|
|
}
|
|
}
|
|
}
|