/* * 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 * * http://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.integration.handler.advice; import java.lang.reflect.Method; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.ProxyMethodInvocation; import org.springframework.integration.context.IntegrationObjectSupport; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHandler; /** * Base class for {@link MessageHandler} advice classes. Subclasses should provide * an implementation for {@link #doInvoke(ExecutionCallback, Object, Message)}. * Used to advise the handleRequestMessage method for {@link AbstractReplyProducingMessageHandler} or * {@link MessageHandler#handleMessage(Message)} for other message handlers. * * @author Gary Russell * @author Artem Bilan * @since 2.2 */ public abstract class AbstractRequestHandlerAdvice extends IntegrationObjectSupport implements MethodInterceptor { protected final Log logger = LogFactory.getLog(this.getClass()); @Override public final Object invoke(final MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); Object[] arguments = invocation.getArguments(); boolean isMessageMethod = (method.getName().equals("handleRequestMessage") || method.getName().equals("handleMessage")) && (arguments.length == 1 && arguments[0] instanceof Message); Object invocationThis = invocation.getThis(); if (!isMessageMethod) { boolean isMessageHandler = invocationThis != null && MessageHandler.class.isAssignableFrom(invocationThis.getClass()); if (!isMessageHandler && this.logger.isWarnEnabled()) { String clazzName = invocationThis == null ? method.getDeclaringClass().getName() : invocationThis.getClass().getName(); this.logger.warn("This advice " + this.getClass().getName() + " can only be used for MessageHandlers; an attempt to advise method '" + method.getName() + "' in '" + clazzName + "' is ignored"); } return invocation.proceed(); } else { Message<?> message = (Message<?>) arguments[0]; try { return doInvoke(new ExecutionCallback() { @Override public Object execute() throws Exception { try { return invocation.proceed(); } catch (Exception e) { //NOSONAR - catch necessary so we can wrap Errors throw e; } catch (Throwable e) { //NOSONAR - ok to catch; unwrapped and rethrown below throw new ThrowableHolderException(e); } } @Override public Object cloneAndExecute() throws Exception { try { /* * If we don't copy the invocation carefully it won't keep a reference to the other * interceptors in the chain. */ if (invocation instanceof ProxyMethodInvocation) { return ((ProxyMethodInvocation) invocation).invocableClone().proceed(); } else { throw new IllegalStateException( "MethodInvocation of the wrong type detected - this should not happen with Spring AOP," + " so please raise an issue if you see this exception"); } } catch (Exception e) { //NOSONAR - catch necessary so we can wrap Errors throw e; } catch (Throwable e) { //NOSONAR - ok to catch; unwrapped and rethrown below throw new ThrowableHolderException(e); } } }, invocationThis, message); } catch (Exception e) { throw this.unwrapThrowableIfNecessary(e); } } } /** * Subclasses implement this method to apply behavior to the {@link MessageHandler}. * <p> * callback.execute() invokes the handler method and returns its result, or null. * * @param callback Subclasses invoke the execute() method on this interface to invoke the handler method. * @param target The target handler. * @param message The message that will be sent to the handler. * @return the result after invoking the {@link MessageHandler}. * @throws Exception Any Exception. */ protected abstract Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception; /** * Unwrap the cause of a {@link AbstractRequestHandlerAdvice.ThrowableHolderException}. * @param e The exception. * @return The cause, or e, if not a {@link AbstractRequestHandlerAdvice.ThrowableHolderException} */ protected Exception unwrapExceptionIfNecessary(Exception e) { Exception actualException = e; if (e instanceof ThrowableHolderException && e.getCause() instanceof Exception) { actualException = (Exception) e.getCause(); } return actualException; } /** * Unwrap the cause of a {@link AbstractRequestHandlerAdvice.ThrowableHolderException}. * @param e The exception. * @return The cause, or e, if not a {@link AbstractRequestHandlerAdvice.ThrowableHolderException} */ protected Throwable unwrapThrowableIfNecessary(Exception e) { Throwable actualThrowable = e; if (e instanceof ThrowableHolderException) { actualThrowable = e.getCause(); } return actualThrowable; } /** * Called by subclasses in doInvoke() to proceed() the invocation. Callers * unwrap {@link AbstractRequestHandlerAdvice.ThrowableHolderException}s and use * the cause for evaluation and re-throwing purposes. * See {@link AbstractRequestHandlerAdvice#unwrapExceptionIfNecessary(Exception)}. */ protected interface ExecutionCallback { /** * Call this for a normal invocation.proceed(). * * @return The result of the execution. * @throws Exception Any Exception. */ Object execute() throws Exception; /** * Call this when it is necessary to clone the invocation before * calling proceed() - such as when the invocation might be called * multiple times - for example in a retry advice. * * @return The result of the execution. * @throws Exception Any Exception. */ Object cloneAndExecute() throws Exception; } @SuppressWarnings("serial") private static final class ThrowableHolderException extends RuntimeException { ThrowableHolderException(Throwable cause) { super(cause); } } }