001/*
002 * Copyright 2002-2018 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.aop.framework.adapter;
018
019import java.lang.reflect.InvocationTargetException;
020import java.lang.reflect.Method;
021import java.util.HashMap;
022import java.util.Map;
023
024import org.aopalliance.intercept.MethodInterceptor;
025import org.aopalliance.intercept.MethodInvocation;
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028
029import org.springframework.aop.AfterAdvice;
030import org.springframework.util.Assert;
031
032/**
033 * Interceptor to wrap an after-throwing advice.
034 *
035 * <p>The signatures on handler methods on the {@code ThrowsAdvice}
036 * implementation method argument must be of the form:<br>
037 *
038 * {@code void afterThrowing([Method, args, target], ThrowableSubclass);}
039 *
040 * <p>Only the last argument is required.
041 *
042 * <p>Some examples of valid methods would be:
043 *
044 * <pre class="code">public void afterThrowing(Exception ex)</pre>
045 * <pre class="code">public void afterThrowing(RemoteException)</pre>
046 * <pre class="code">public void afterThrowing(Method method, Object[] args, Object target, Exception ex)</pre>
047 * <pre class="code">public void afterThrowing(Method method, Object[] args, Object target, ServletException ex)</pre>
048 *
049 * <p>This is a framework class that need not be used directly by Spring users.
050 *
051 * @author Rod Johnson
052 * @author Juergen Hoeller
053 * @see MethodBeforeAdviceInterceptor
054 * @see AfterReturningAdviceInterceptor
055 */
056public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice {
057
058        private static final String AFTER_THROWING = "afterThrowing";
059
060        private static final Log logger = LogFactory.getLog(ThrowsAdviceInterceptor.class);
061
062
063        private final Object throwsAdvice;
064
065        /** Methods on throws advice, keyed by exception class */
066        private final Map<Class<?>, Method> exceptionHandlerMap = new HashMap<Class<?>, Method>();
067
068
069        /**
070         * Create a new ThrowsAdviceInterceptor for the given ThrowsAdvice.
071         * @param throwsAdvice the advice object that defines the exception handler methods
072         * (usually a {@link org.springframework.aop.ThrowsAdvice} implementation)
073         */
074        public ThrowsAdviceInterceptor(Object throwsAdvice) {
075                Assert.notNull(throwsAdvice, "Advice must not be null");
076                this.throwsAdvice = throwsAdvice;
077
078                Method[] methods = throwsAdvice.getClass().getMethods();
079                for (Method method : methods) {
080                        if (method.getName().equals(AFTER_THROWING)) {
081                                Class<?>[] paramTypes = method.getParameterTypes();
082                                if (paramTypes.length == 1 || paramTypes.length == 4) {
083                                        Class<?> throwableParam = paramTypes[paramTypes.length - 1];
084                                        if (Throwable.class.isAssignableFrom(throwableParam)) {
085                                                // An exception handler to register...
086                                                this.exceptionHandlerMap.put(throwableParam, method);
087                                                if (logger.isDebugEnabled()) {
088                                                        logger.debug("Found exception handler method on throws advice: " + method);
089                                                }
090                                        }
091                                }
092                        }
093                }
094
095                if (this.exceptionHandlerMap.isEmpty()) {
096                        throw new IllegalArgumentException(
097                                        "At least one handler method must be found in class [" + throwsAdvice.getClass() + "]");
098                }
099        }
100
101
102        /**
103         * Return the number of handler methods in this advice.
104         */
105        public int getHandlerMethodCount() {
106                return this.exceptionHandlerMap.size();
107        }
108
109
110        @Override
111        public Object invoke(MethodInvocation mi) throws Throwable {
112                try {
113                        return mi.proceed();
114                }
115                catch (Throwable ex) {
116                        Method handlerMethod = getExceptionHandler(ex);
117                        if (handlerMethod != null) {
118                                invokeHandlerMethod(mi, ex, handlerMethod);
119                        }
120                        throw ex;
121                }
122        }
123
124        /**
125         * Determine the exception handle method for the given exception.
126         * @param exception the exception thrown
127         * @return a handler for the given exception type, or {@code null} if none found
128         */
129        private Method getExceptionHandler(Throwable exception) {
130                Class<?> exceptionClass = exception.getClass();
131                if (logger.isTraceEnabled()) {
132                        logger.trace("Trying to find handler for exception of type [" + exceptionClass.getName() + "]");
133                }
134                Method handler = this.exceptionHandlerMap.get(exceptionClass);
135                while (handler == null && exceptionClass != Throwable.class) {
136                        exceptionClass = exceptionClass.getSuperclass();
137                        handler = this.exceptionHandlerMap.get(exceptionClass);
138                }
139                if (handler != null && logger.isDebugEnabled()) {
140                        logger.debug("Found handler for exception of type [" + exceptionClass.getName() + "]: " + handler);
141                }
142                return handler;
143        }
144
145        private void invokeHandlerMethod(MethodInvocation mi, Throwable ex, Method method) throws Throwable {
146                Object[] handlerArgs;
147                if (method.getParameterTypes().length == 1) {
148                        handlerArgs = new Object[] {ex};
149                }
150                else {
151                        handlerArgs = new Object[] {mi.getMethod(), mi.getArguments(), mi.getThis(), ex};
152                }
153                try {
154                        method.invoke(this.throwsAdvice, handlerArgs);
155                }
156                catch (InvocationTargetException targetEx) {
157                        throw targetEx.getTargetException();
158                }
159        }
160
161}