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.lang.Nullable;
031import org.springframework.util.Assert;
032
033/**
034 * Interceptor to wrap an after-throwing advice.
035 *
036 * <p>The signatures on handler methods on the {@code ThrowsAdvice}
037 * implementation method argument must be of the form:<br>
038 *
039 * {@code void afterThrowing([Method, args, target], ThrowableSubclass);}
040 *
041 * <p>Only the last argument is required.
042 *
043 * <p>Some examples of valid methods would be:
044 *
045 * <pre class="code">public void afterThrowing(Exception ex)</pre>
046 * <pre class="code">public void afterThrowing(RemoteException)</pre>
047 * <pre class="code">public void afterThrowing(Method method, Object[] args, Object target, Exception ex)</pre>
048 * <pre class="code">public void afterThrowing(Method method, Object[] args, Object target, ServletException ex)</pre>
049 *
050 * <p>This is a framework class that need not be used directly by Spring users.
051 *
052 * @author Rod Johnson
053 * @author Juergen Hoeller
054 * @see MethodBeforeAdviceInterceptor
055 * @see AfterReturningAdviceInterceptor
056 */
057public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice {
058
059        private static final String AFTER_THROWING = "afterThrowing";
060
061        private static final Log logger = LogFactory.getLog(ThrowsAdviceInterceptor.class);
062
063
064        private final Object throwsAdvice;
065
066        /** Methods on throws advice, keyed by exception class. */
067        private final Map<Class<?>, Method> exceptionHandlerMap = new HashMap<>();
068
069
070        /**
071         * Create a new ThrowsAdviceInterceptor for the given ThrowsAdvice.
072         * @param throwsAdvice the advice object that defines the exception handler methods
073         * (usually a {@link org.springframework.aop.ThrowsAdvice} implementation)
074         */
075        public ThrowsAdviceInterceptor(Object throwsAdvice) {
076                Assert.notNull(throwsAdvice, "Advice must not be null");
077                this.throwsAdvice = throwsAdvice;
078
079                Method[] methods = throwsAdvice.getClass().getMethods();
080                for (Method method : methods) {
081                        if (method.getName().equals(AFTER_THROWING) &&
082                                        (method.getParameterCount() == 1 || method.getParameterCount() == 4)) {
083                                Class<?> throwableParam = method.getParameterTypes()[method.getParameterCount() - 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                if (this.exceptionHandlerMap.isEmpty()) {
095                        throw new IllegalArgumentException(
096                                        "At least one handler method must be found in class [" + throwsAdvice.getClass() + "]");
097                }
098        }
099
100
101        /**
102         * Return the number of handler methods in this advice.
103         */
104        public int getHandlerMethodCount() {
105                return this.exceptionHandlerMap.size();
106        }
107
108
109        @Override
110        public Object invoke(MethodInvocation mi) throws Throwable {
111                try {
112                        return mi.proceed();
113                }
114                catch (Throwable ex) {
115                        Method handlerMethod = getExceptionHandler(ex);
116                        if (handlerMethod != null) {
117                                invokeHandlerMethod(mi, ex, handlerMethod);
118                        }
119                        throw ex;
120                }
121        }
122
123        /**
124         * Determine the exception handle method for the given exception.
125         * @param exception the exception thrown
126         * @return a handler for the given exception type, or {@code null} if none found
127         */
128        @Nullable
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.isTraceEnabled()) {
140                        logger.trace("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.getParameterCount() == 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}