001/* 002 * Copyright 2002-2017 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.messaging.handler.invocation; 018 019import java.lang.reflect.Method; 020import java.util.ArrayList; 021import java.util.Collections; 022import java.util.List; 023import java.util.Map; 024import java.util.concurrent.ConcurrentHashMap; 025 026import org.springframework.core.ExceptionDepthComparator; 027import org.springframework.util.Assert; 028import org.springframework.util.ClassUtils; 029 030/** 031 * Cache exception handling method mappings and provide options to look up a method 032 * that should handle an exception. If multiple methods match, they are sorted using 033 * {@link ExceptionDepthComparator} and the top match is returned. 034 * 035 * @author Rossen Stoyanchev 036 * @author Juergen Hoeller 037 * @since 4.0 038 */ 039public abstract class AbstractExceptionHandlerMethodResolver { 040 041 private static final Method NO_METHOD_FOUND = 042 ClassUtils.getMethodIfAvailable(System.class, "currentTimeMillis"); 043 044 private final Map<Class<? extends Throwable>, Method> mappedMethods = 045 new ConcurrentHashMap<Class<? extends Throwable>, Method>(16); 046 047 private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = 048 new ConcurrentHashMap<Class<? extends Throwable>, Method>(16); 049 050 051 /** 052 * Protected constructor accepting exception-to-method mappings. 053 */ 054 protected AbstractExceptionHandlerMethodResolver(Map<Class<? extends Throwable>, Method> mappedMethods) { 055 Assert.notNull(mappedMethods, "Mapped Methods must not be null"); 056 this.mappedMethods.putAll(mappedMethods); 057 } 058 059 /** 060 * Extract the exceptions this method handles.This implementation looks for 061 * sub-classes of Throwable in the method signature. 062 * The method is static to ensure safe use from sub-class constructors. 063 */ 064 @SuppressWarnings("unchecked") 065 protected static List<Class<? extends Throwable>> getExceptionsFromMethodSignature(Method method) { 066 List<Class<? extends Throwable>> result = new ArrayList<Class<? extends Throwable>>(); 067 for (Class<?> paramType : method.getParameterTypes()) { 068 if (Throwable.class.isAssignableFrom(paramType)) { 069 result.add((Class<? extends Throwable>) paramType); 070 } 071 } 072 if (result.isEmpty()) { 073 throw new IllegalStateException("No exception types mapped to " + method); 074 } 075 return result; 076 } 077 078 079 /** 080 * Whether the contained type has any exception mappings. 081 */ 082 public boolean hasExceptionMappings() { 083 return !this.mappedMethods.isEmpty(); 084 } 085 086 /** 087 * Find a {@link Method} to handle the given exception. 088 * Use {@link ExceptionDepthComparator} if more than one match is found. 089 * @param exception the exception 090 * @return a Method to handle the exception, or {@code null} if none found 091 */ 092 public Method resolveMethod(Exception exception) { 093 Method method = resolveMethodByExceptionType(exception.getClass()); 094 if (method == null) { 095 Throwable cause = exception.getCause(); 096 if (cause != null) { 097 method = resolveMethodByExceptionType(cause.getClass()); 098 } 099 } 100 return method; 101 } 102 103 /** 104 * Find a {@link Method} to handle the given exception type. This can be 105 * useful if an {@link Exception} instance is not available (e.g. for tools). 106 * @param exceptionType the exception type 107 * @return a Method to handle the exception, or {@code null} if none found 108 * @since 4.3.1 109 */ 110 public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) { 111 Method method = this.exceptionLookupCache.get(exceptionType); 112 if (method == null) { 113 method = getMappedMethod(exceptionType); 114 this.exceptionLookupCache.put(exceptionType, method != null ? method : NO_METHOD_FOUND); 115 } 116 return method != NO_METHOD_FOUND ? method : null; 117 } 118 119 /** 120 * Return the {@link Method} mapped to the given exception type, or {@code null} if none. 121 */ 122 private Method getMappedMethod(Class<? extends Throwable> exceptionType) { 123 List<Class<? extends Throwable>> matches = new ArrayList<Class<? extends Throwable>>(); 124 for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) { 125 if (mappedException.isAssignableFrom(exceptionType)) { 126 matches.add(mappedException); 127 } 128 } 129 if (!matches.isEmpty()) { 130 Collections.sort(matches, new ExceptionDepthComparator(exceptionType)); 131 return this.mappedMethods.get(matches.get(0)); 132 } 133 else { 134 return null; 135 } 136 } 137 138}