diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/ExpressionControlBusFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/ExpressionControlBusFactoryBean.java index 8190ef073e..3ef16dc8f3 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/ExpressionControlBusFactoryBean.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/ExpressionControlBusFactoryBean.java @@ -13,15 +13,27 @@ package org.springframework.integration.config; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.context.Lifecycle; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.expression.BeanResolver; +import org.springframework.expression.MethodFilter; import org.springframework.integration.core.MessageHandler; import org.springframework.integration.handler.ExpressionCommandMessageProcessor; import org.springframework.integration.handler.ServiceActivatingHandler; +import org.springframework.jmx.export.annotation.ManagedAttribute; +import org.springframework.jmx.export.annotation.ManagedOperation; +import org.springframework.util.CustomizableThreadCreator; /** * FactoryBean for creating {@link MessageHandler} instances to handle a message as a SpEL expression. * * @author Dave Syer + * @author Mark Fisher * @since 2.0 */ public class ExpressionControlBusFactoryBean extends AbstractSimpleMessageHandlerFactoryBean { @@ -30,7 +42,7 @@ public class ExpressionControlBusFactoryBean extends AbstractSimpleMessageHandle private volatile BeanResolver beanResolver; - private final ExpressionCommandMessageProcessor processor = new ExpressionCommandMessageProcessor(); + private final MethodFilter methodFilter = new ControlBusMethodFilter(); public void setSendTimeout(Long sendTimeout) { @@ -42,15 +54,50 @@ public class ExpressionControlBusFactoryBean extends AbstractSimpleMessageHandle } protected MessageHandler createHandler() { - this.processor.setBeanFactory(this.getBeanFactory()); + ExpressionCommandMessageProcessor processor = new ExpressionCommandMessageProcessor(this.methodFilter); + processor.setBeanFactory(this.getBeanFactory()); if (this.beanResolver != null) { - this.processor.setBeanResolver(this.beanResolver); + processor.setBeanResolver(this.beanResolver); } - ServiceActivatingHandler handler = new ServiceActivatingHandler(this.processor); + ServiceActivatingHandler handler = new ServiceActivatingHandler(processor); if (this.sendTimeout != null) { handler.setSendTimeout(this.sendTimeout); } return handler; } + + private static class ControlBusMethodFilter implements MethodFilter { + + public List filter(List methods) { + List supportedMethods = new ArrayList(); + for (Method method : methods) { + if (this.accept(method)) { + supportedMethods.add(method); + } + } + return supportedMethods; + } + + private boolean accept(Method method) { + if (method.getDeclaringClass().equals(Lifecycle.class)) { + return true; + } + if (CustomizableThreadCreator.class.isAssignableFrom(method.getDeclaringClass()) + && (method.getName().startsWith("get") + || method.getName().startsWith("set") + || method.getName().startsWith("shutdown"))) { + return true; + } + if (this.hasAnnotation(method, ManagedAttribute.class) || this.hasAnnotation(method, ManagedOperation.class)) { + return true; + } + return false; + } + + private boolean hasAnnotation(Method method, Class annotationType) { + return AnnotationUtils.findAnnotation(method, annotationType) != null; + } + } + } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/handler/ExpressionCommandMessageProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/handler/ExpressionCommandMessageProcessor.java index 12f90b4cfb..05d9b185f0 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/handler/ExpressionCommandMessageProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/handler/ExpressionCommandMessageProcessor.java @@ -13,7 +13,20 @@ package org.springframework.integration.handler; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.expression.AccessException; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.EvaluationException; import org.springframework.expression.Expression; +import org.springframework.expression.MethodExecutor; +import org.springframework.expression.MethodFilter; +import org.springframework.expression.MethodResolver; +import org.springframework.expression.spel.support.ReflectiveMethodResolver; import org.springframework.integration.Message; /** @@ -26,6 +39,17 @@ import org.springframework.integration.Message; */ public class ExpressionCommandMessageProcessor extends AbstractMessageProcessor { + public ExpressionCommandMessageProcessor() { + } + + public ExpressionCommandMessageProcessor(MethodFilter methodFilter) { + if (methodFilter != null) { + MethodResolver methodResolver = new ExpressionCommandMethodResolver(methodFilter); + this.getEvaluationContext().setMethodResolvers(Collections.singletonList(methodResolver)); + } + } + + /** * Evaluates the Message payload expression as a command. * @throws IllegalArgumentException if the payload is not an Exception or String @@ -41,4 +65,42 @@ public class ExpressionCommandMessageProcessor extends AbstractMessageProcessor< throw new IllegalArgumentException("Message payload must be an Expression instance or an expression String."); } + + private static class ExpressionCommandMethodResolver extends ReflectiveMethodResolver { + + private final MethodFilter methodFilter; + + + private ExpressionCommandMethodResolver(MethodFilter methodFilter) { + this.methodFilter = methodFilter; + } + + + public MethodExecutor resolve(EvaluationContext context, + Object targetObject, String name, List argumentTypes) throws AccessException { + this.validateMethod(targetObject, name, (argumentTypes != null ? argumentTypes.size() : 0)); + return super.resolve(context, targetObject, name, argumentTypes); + } + + private void validateMethod(Object targetObject, String name, int argumentCount) { + if (this.methodFilter == null) { + return; + } + Class type = (targetObject instanceof Class ? (Class) targetObject : targetObject.getClass()); + Method[] methods = type.getMethods(); + List candidates = new ArrayList(); + for (Method method : methods) { + if (method.getName().equals(name) && method.getParameterTypes().length == argumentCount) { + candidates.add(method); + } + } + List supportedMethods = this.methodFilter.filter(candidates); + if (supportedMethods.size() == 0) { + String methodDescription = (candidates.size() > 0) ? candidates.get(0).toString() : name; + throw new EvaluationException("The method '" + methodDescription + "' is not supported by this command processor. " + + "If using the Control Bus, consider adding @ManagedOperation or @ManagedAttribute."); + } + } + } + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusBeanResolverTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusBeanResolverTests.java index e3e1573d18..b24124f2aa 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusBeanResolverTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusBeanResolverTests.java @@ -29,6 +29,7 @@ import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; import org.springframework.integration.core.PollableChannel; import org.springframework.integration.support.MessageBuilder; +import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -60,9 +61,13 @@ public class ControlBusBeanResolverTests { } } + public static class Service { + + @ManagedOperation public String convert(String input) { return "cat"; } } + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusExplicitPollerTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusExplicitPollerTests.java index c55098edea..d384ffe3ed 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusExplicitPollerTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusExplicitPollerTests.java @@ -26,6 +26,7 @@ import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; import org.springframework.integration.core.PollableChannel; import org.springframework.integration.support.MessageBuilder; +import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -51,9 +52,13 @@ public class ControlBusExplicitPollerTests { assertNull(output.receive(0)); } + public static class Service { + + @ManagedOperation public String convert(String input) { return "cat"; } } + } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusTests.java index c5509345d7..5fbacf7477 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/ControlBusTests.java @@ -26,6 +26,7 @@ import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; import org.springframework.integration.core.PollableChannel; import org.springframework.integration.support.MessageBuilder; +import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -51,9 +52,13 @@ public class ControlBusTests { assertNull(output.receive(0)); } + public static class Service { + + @ManagedOperation public String convert(String input) { return "cat"; } } + }