All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.security.web.util.ThrowableAnalyzer Maven / Gradle / Ivy

/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.security.web.util;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import org.springframework.util.Assert;

/**
 * Handler for analyzing {@link Throwable} instances.
 *
 * Can be subclassed to customize its behavior.
 *
 * @author Andreas Senft
 * @since 2.0
 */
public class ThrowableAnalyzer {

	/**
	 * Default extractor for {@link Throwable} instances.
	 *
	 * @see Throwable#getCause()
	 */
	public static final ThrowableCauseExtractor DEFAULT_EXTRACTOR = (throwable) -> throwable.getCause();

	/**
	 * Default extractor for {@link InvocationTargetException} instances.
	 *
	 * @see InvocationTargetException#getTargetException()
	 */
	public static final ThrowableCauseExtractor INVOCATIONTARGET_EXTRACTOR = (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 = (class1, class2) -> { if (class1.isAssignableFrom(class2)) { return 1; } if (class2.isAssignableFrom(class1)) { return -1; } return class1.getName().compareTo(class2.getName()); }; /** * Map of registered cause extractors. key: Class<Throwable>; value: * ThrowableCauseExctractor */ private final Map, ThrowableCauseExtractor> 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) { Assert.notNull(extractor, "Invalid extractor: null"); this.extractorMap.put(throwableType, extractor); } /** * Initializes associations between Throwables and * ThrowableCauseExtractors. 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 */ @SuppressWarnings("unchecked") final Class[] getRegisteredTypes() { Set> typeList = this.extractorMap.keySet(); return typeList.toArray(new Class[0]); } /** * 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) { Assert.notNull(throwable, "Invalid throwable: null"); List chain = new ArrayList<>(); Throwable currentThrowable = throwable; while (currentThrowable != null) { chain.add(currentThrowable); currentThrowable = extractCause(currentThrowable); } return chain.toArray(new Throwable[0]); } /** * 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 (Map.Entry, ThrowableCauseExtractor> entry : this.extractorMap.entrySet()) { Class throwableType = entry.getKey(); if (throwableType.isInstance(throwable)) { ThrowableCauseExtractor extractor = 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) { if (chain != null) { for (Throwable t : chain) { if ((t != null) && throwableType.isInstance(t)) { return t; } } } return null; } /** * 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 void verifyThrowableHierarchy(Throwable throwable, Class expectedBaseType) { if (expectedBaseType == null) { return; } Assert.notNull(throwable, "Invalid throwable: null"); Class throwableType = throwable.getClass(); Assert.isTrue(expectedBaseType.isAssignableFrom(throwableType), () -> "Invalid type: '" + throwableType.getName() + "'. Has to be a subclass of '" + expectedBaseType.getName() + "'"); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy