SpringSecurityException with the specified
* message and root cause.
*
@@ -39,7 +39,7 @@ public abstract class SpringSecurityException extends NestedRuntimeException {
super(msg, t);
}
-/**
+ /**
* Constructs an SpringSecurityException with the specified
* message and no root cause.
*
diff --git a/core/src/main/java/org/springframework/security/ui/ExceptionTranslationFilter.java b/core/src/main/java/org/springframework/security/ui/ExceptionTranslationFilter.java
index 9ce835bea4..711a5de4ed 100644
--- a/core/src/main/java/org/springframework/security/ui/ExceptionTranslationFilter.java
+++ b/core/src/main/java/org/springframework/security/ui/ExceptionTranslationFilter.java
@@ -25,6 +25,8 @@ import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.ui.savedrequest.SavedRequest;
import org.springframework.security.util.PortResolver;
import org.springframework.security.util.PortResolverImpl;
+import org.springframework.security.util.ThrowableAnalyzer;
+import org.springframework.security.util.ThrowableCauseExtractor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -92,7 +94,8 @@ public class ExceptionTranslationFilter extends SpringSecurityFilter implements
private AuthenticationEntryPoint authenticationEntryPoint;
private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
private PortResolver portResolver = new PortResolverImpl();
- private boolean createSessionAllowed = true;
+ private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();
+ private boolean createSessionAllowed = true;
//~ Methods ========================================================================================================
@@ -100,7 +103,8 @@ public class ExceptionTranslationFilter extends SpringSecurityFilter implements
Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint must be specified");
Assert.notNull(portResolver, "portResolver must be specified");
Assert.notNull(authenticationTrustResolver, "authenticationTrustResolver must be specified");
- }
+ Assert.notNull(throwableAnalyzer, "throwableAnalyzer must be specified");
+ }
public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException,
ServletException {
@@ -112,24 +116,31 @@ public class ExceptionTranslationFilter extends SpringSecurityFilter implements
logger.debug("Chain processed normally");
}
}
- catch (AuthenticationException ex) {
- handleException(request, response, chain, ex);
- }
- catch (AccessDeniedException ex) {
- handleException(request, response, chain, ex);
- }
- catch (ServletException ex) {
- if (ex.getRootCause() instanceof AuthenticationException
- || ex.getRootCause() instanceof AccessDeniedException) {
- handleException(request, response, chain, (SpringSecurityException) ex.getRootCause());
- }
- else {
- throw ex;
- }
- }
- catch (IOException ex) {
- throw ex;
- }
+ catch (IOException ex) {
+ throw ex;
+ }
+ catch (Exception ex) {
+ // Try to extract a SpringSecurityException from the stacktrace
+ Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
+ SpringSecurityException ase = (SpringSecurityException)
+ this.throwableAnalyzer.getFirstThrowableOfType(SpringSecurityException.class, causeChain);
+
+ if (ase != null) {
+ handleException(request, response, chain, ase);
+ }
+ else {
+ // Rethrow ServletExceptions and RuntimeExceptions as-is
+ if (ex instanceof ServletException) {
+ throw (ServletException) ex;
+ }
+ else if (ex instanceof RuntimeException) {
+ throw (RuntimeException) ex;
+ }
+
+ // Wrap other Exceptions. These are not expected to happen
+ throw new RuntimeException(ex);
+ }
+ }
}
public AuthenticationEntryPoint getAuthenticationEntryPoint() {
@@ -235,7 +246,33 @@ public class ExceptionTranslationFilter extends SpringSecurityFilter implements
this.portResolver = portResolver;
}
+ public void setThrowableAnalyzer(ThrowableAnalyzer throwableAnalyzer) {
+ this.throwableAnalyzer = throwableAnalyzer;
+ }
+
public int getOrder() {
return FilterChainOrder.EXCEPTION_TRANSLATION_FILTER;
}
+
+ /**
+ * Default implementation of ThrowableAnalyzer which is capable of also unwrapping
+ * ServletExceptions.
+ */
+ private static final class DefaultThrowableAnalyzer extends ThrowableAnalyzer {
+ /**
+ * @see org.springframework.security.util.ThrowableAnalyzer#initExtractorMap()
+ */
+ protected void initExtractorMap() {
+ super.initExtractorMap();
+
+ registerExtractor(ServletException.class, new ThrowableCauseExtractor() {
+ public Throwable extractCause(Throwable throwable) {
+ ThrowableAnalyzer.verifyThrowableHierarchy(throwable, ServletException.class);
+ return ((ServletException) throwable).getRootCause();
+ }
+ });
+ }
+
+ }
+
}
diff --git a/core/src/main/java/org/springframework/security/util/ThrowableAnalyzer.java b/core/src/main/java/org/springframework/security/util/ThrowableAnalyzer.java
new file mode 100755
index 0000000000..fab60bc62d
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/util/ThrowableAnalyzer.java
@@ -0,0 +1,270 @@
+package org.springframework.security.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 Andreas Senft
+ * @since 2.0
+ * @version $Id$
+ */
+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.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 Throwables and ThrowableCauseExtractors.
+ * The default implementation performs the following registrations:
+ *
+ * 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() + "'");
+ }
+ }
+}
diff --git a/core/src/main/java/org/springframework/security/util/ThrowableCauseExtractor.java b/core/src/main/java/org/springframework/security/util/ThrowableCauseExtractor.java
new file mode 100755
index 0000000000..3ac13ecb7f
--- /dev/null
+++ b/core/src/main/java/org/springframework/security/util/ThrowableCauseExtractor.java
@@ -0,0 +1,25 @@
+package org.springframework.security.util;
+
+
+/**
+ * Interface for handlers extracting the cause out of a specific {@link Throwable} type.
+ *
+ * @author Andreas Senft
+ * @since 2.0
+ * @version $Id$
+ *
+ * @see ThrowableAnalyzer
+ */
+public interface ThrowableCauseExtractor {
+
+ /**
+ * Extracts the cause from the provided Throwable.
+ *
+ * @param throwable the Throwable
+ * @return the extracted cause (maybe null)
+ *
+ * @throws IllegalArgumentException if throwable is null
+ * or otherwise considered invalid for the implementation
+ */
+ Throwable extractCause(Throwable throwable);
+}
diff --git a/core/src/test/java/org/springframework/security/ui/ExceptionTranslationFilterTests.java b/core/src/test/java/org/springframework/security/ui/ExceptionTranslationFilterTests.java
index 8f3279f5a2..f8404ebbb7 100644
--- a/core/src/test/java/org/springframework/security/ui/ExceptionTranslationFilterTests.java
+++ b/core/src/test/java/org/springframework/security/ui/ExceptionTranslationFilterTests.java
@@ -46,19 +46,8 @@ import javax.servlet.ServletResponse;
* benalex $
*/
public class ExceptionTranslationFilterTests extends TestCase {
- //~ Constructors ===================================================================================================
-
- public ExceptionTranslationFilterTests() {
- super();
- }
-
- public ExceptionTranslationFilterTests(String arg0) {
- super(arg0);
- }
-
//~ Methods ========================================================================================================
-
protected void tearDown() throws Exception {
super.tearDown();
SecurityContextHolder.clearContext();
@@ -312,8 +301,7 @@ public class ExceptionTranslationFilterTests extends TestCase {
}
}
- // ~ Inner Classes
- // ==================================================================================================
+ // ~ Inner Classes =================================================================================================
private class MockFilterChain implements FilterChain {
private boolean throwAccessDenied;
diff --git a/core/src/test/java/org/springframework/security/util/ThrowableAnalyzerTests.java b/core/src/test/java/org/springframework/security/util/ThrowableAnalyzerTests.java
new file mode 100644
index 0000000000..0392638483
--- /dev/null
+++ b/core/src/test/java/org/springframework/security/util/ThrowableAnalyzerTests.java
@@ -0,0 +1,274 @@
+package org.springframework.security.util;
+
+import java.lang.reflect.InvocationTargetException;
+
+import junit.framework.TestCase;
+
+/**
+ * Testcases for {@link ThrowableAnalyzer}.
+ *
+ * @author Andreas Senft
+ */
+public class ThrowableAnalyzerTests extends TestCase {
+
+ /**
+ * Exception for testing purposes. The cause is not retrievable by {@link #getCause()}.
+ */
+ public static final class NonStandardException extends Exception {
+
+ private Throwable cause;
+
+ public NonStandardException(String message, Throwable cause) {
+ super(message);
+ this.cause = cause;
+ }
+
+ public Throwable resolveCause() {
+ return this.cause;
+ }
+ }
+
+ /**
+ * ThrowableCauseExtractor for handling NonStandardException instances.
+ */
+ public static final class NonStandardExceptionCauseExtractor implements ThrowableCauseExtractor {
+
+ public Throwable extractCause(Throwable throwable) {
+ ThrowableAnalyzer.verifyThrowableHierarchy(throwable, NonStandardException.class);
+ return ((NonStandardException) throwable).resolveCause();
+ }
+
+ }
+
+
+ /**
+ * An array of nested throwables for testing.
+ * The cause of element 0 is element 1, the cause of element 1 is element 2 and so on.
+ */
+ private Throwable[] testTrace;
+
+ /**
+ * Plain ThrowableAnalyzer.
+ */
+ private ThrowableAnalyzer standardAnalyzer;
+
+ /**
+ * Enhanced ThrowableAnalyzer capable to process
+ * NonStandardExceptions.
+ */
+ private ThrowableAnalyzer nonstandardAnalyzer;
+
+ /**
+ * @see junit.framework.TestCase#setUp()
+ */
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ // Set up test trace
+ this.testTrace = new Throwable[7];
+ this.testTrace[6] = new IllegalArgumentException("Test_6");
+ this.testTrace[5] = new Throwable("Test_5",this.testTrace[6]);
+ this.testTrace[4] = new InvocationTargetException(this.testTrace[5], "Test_4");
+ this.testTrace[3] = new Exception("Test_3", this.testTrace[4]);
+ this.testTrace[2] = new NonStandardException("Test_2", this.testTrace[3]);
+ this.testTrace[1] = new RuntimeException("Test_1", this.testTrace[2]);
+ this.testTrace[0] = new Exception("Test_0", this.testTrace[1]);
+
+ // Set up standard analyzer
+ this.standardAnalyzer = new ThrowableAnalyzer();
+
+ // Set up nonstandard analyzer
+ this.nonstandardAnalyzer = new ThrowableAnalyzer() {
+ /**
+ * @see org.springframework.security.util.ThrowableAnalyzer#initExtractorMap()
+ */
+ @Override
+ protected void initExtractorMap() {
+ super.initExtractorMap();
+ // register extractor for NonStandardException
+ registerExtractor(NonStandardException.class, new NonStandardExceptionCauseExtractor());
+ }
+ };
+ }
+
+
+ /**
+ * @see junit.framework.TestCase#tearDown()
+ */
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+
+ public void testRegisterExtractorWithInvalidClass() {
+ try {
+ new ThrowableAnalyzer() {
+
+ /**
+ * @see org.springframework.security.util.ThrowableAnalyzer#initExtractorMap()
+ */
+ @Override
+ protected void initExtractorMap() {
+ // Object is no subclass of Throwable
+ super.registerExtractor(Object.class, DEFAULT_EXTRACTOR);
+ }
+ };
+
+ fail("IllegalArgumentExpected");
+ } catch (IllegalArgumentException e) {
+ // ok
+ }
+ }
+
+ public void testRegisterExtractorWithInvalidExtractor() {
+ try {
+ new ThrowableAnalyzer() {
+
+ /**
+ * @see org.springframework.security.util.ThrowableAnalyzer#initExtractorMap()
+ */
+ @Override
+ protected void initExtractorMap() {
+ // null is no valid extractor
+ super.registerExtractor(Exception.class, null);
+ }
+ };
+
+ fail("IllegalArgumentExpected");
+ } catch (IllegalArgumentException e) {
+ // ok
+ }
+ }
+
+ public void testGetRegisteredTypes() {
+
+ Class[] registeredTypes = this.nonstandardAnalyzer.getRegisteredTypes();
+
+ for (int i = 0; i < registeredTypes.length; ++i) {
+ Class clazz = registeredTypes[i];
+
+ // The most specific types have to occur first.
+ for (int j = 0; j < i; ++j) {
+ Class prevClazz = registeredTypes[j];
+
+ assertFalse("Unexpected order of registered classes: "
+ + prevClazz + " is assignable from " + clazz, prevClazz.isAssignableFrom(clazz));
+ }
+ }
+ }
+
+ public void testDetermineCauseChainWithNoExtractors() {
+ ThrowableAnalyzer analyzer = new ThrowableAnalyzer() {
+
+ /**
+ * @see org.springframework.security.util.ThrowableAnalyzer#initExtractorMap()
+ */
+ @Override
+ protected void initExtractorMap() {
+ // skip default initialization
+ }
+ };
+
+ assertEquals("Unexpected number of registered types", 0, analyzer.getRegisteredTypes().length);
+
+ Throwable t = this.testTrace[0];
+ Throwable[] chain = analyzer.determineCauseChain(t);
+ // Without extractors only the root throwable is available
+ assertEquals("Unexpected chain size", 1, chain.length);
+ assertEquals("Unexpected chain entry", t, chain[0]);
+ }
+
+ public void testDetermineCauseChainWithDefaultExtractors() {
+ ThrowableAnalyzer analyzer = this.standardAnalyzer;
+
+ assertEquals("Unexpected number of registered types", 2, analyzer.getRegisteredTypes().length);
+
+ Throwable[] chain = analyzer.determineCauseChain(this.testTrace[0]);
+
+ // Element at index 2 is a NonStandardException which cannot be analyzed further by default
+ assertEquals("Unexpected chain size", 3, chain.length);
+ for (int i = 0; i < 3; ++i) {
+ assertEquals("Unexpected chain entry: " + i, this.testTrace[i], chain[i]);
+ }
+ }
+
+ public void testDetermineCauseChainWithCustomExtractors() {
+ ThrowableAnalyzer analyzer = this.nonstandardAnalyzer;
+
+ Throwable[] chain = analyzer.determineCauseChain(this.testTrace[0]);
+
+ assertEquals("Unexpected chain size", this.testTrace.length, chain.length);
+ for (int i = 0; i < chain.length; ++i) {
+ assertEquals("Unexpected chain entry: " + i, this.testTrace[i], chain[i]);
+ }
+ }
+
+ public void testGetFirstThrowableOfTypeWithSuccess1() {
+ ThrowableAnalyzer analyzer = this.nonstandardAnalyzer;
+
+ Throwable[] chain = analyzer.determineCauseChain(this.testTrace[0]);
+
+ Throwable result = analyzer.getFirstThrowableOfType(Exception.class, chain);
+
+ assertNotNull("null not expected", result);
+ assertEquals("Unexpected throwable found", this.testTrace[0], result);
+ }
+
+ public void testGetFirstThrowableOfTypeWithSuccess2() {
+ ThrowableAnalyzer analyzer = this.nonstandardAnalyzer;
+
+ Throwable[] chain = analyzer.determineCauseChain(this.testTrace[0]);
+
+ Throwable result = analyzer.getFirstThrowableOfType(NonStandardException.class, chain);
+
+ assertNotNull("null not expected", result);
+ assertEquals("Unexpected throwable found", this.testTrace[2], result);
+ }
+
+ public void testGetFirstThrowableOfTypeWithFailure() {
+ ThrowableAnalyzer analyzer = this.nonstandardAnalyzer;
+
+ Throwable[] chain = analyzer.determineCauseChain(this.testTrace[0]);
+
+ // IllegalStateException not in trace
+ Throwable result = analyzer.getFirstThrowableOfType(IllegalStateException.class, chain);
+
+ assertNull("null expected", result);
+ }
+
+ public void testVerifyThrowableHierarchyWithExactType() {
+
+ Throwable throwable = new IllegalStateException("Test");
+ ThrowableAnalyzer.verifyThrowableHierarchy(throwable, IllegalStateException.class);
+ // No exception expected
+ }
+
+ public void testVerifyThrowableHierarchyWithCompatibleType() {
+
+ Throwable throwable = new IllegalStateException("Test");
+ ThrowableAnalyzer.verifyThrowableHierarchy(throwable, Exception.class);
+ // No exception expected
+ }
+
+ public void testVerifyThrowableHierarchyWithNull() {
+ try {
+ ThrowableAnalyzer.verifyThrowableHierarchy(null, Throwable.class);
+ fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException e) {
+ // ok
+ }
+ }
+
+ public void testVerifyThrowableHierarchyWithNonmatchingType() {
+
+ Throwable throwable = new IllegalStateException("Test");
+ try {
+ ThrowableAnalyzer.verifyThrowableHierarchy(throwable, InvocationTargetException.class);
+ fail("IllegalArgumentException expected");
+ } catch (IllegalArgumentException e) {
+ // ok
+ }
+ }
+}