diff --git a/org.synyx.hera.si/src/main/java/org/synyx/hera/si/PluginRegistryAwareMessageHandler.java b/org.synyx.hera.si/src/main/java/org/synyx/hera/si/PluginRegistryAwareMessageHandler.java index 91c8810..0bf8f56 100644 --- a/org.synyx.hera.si/src/main/java/org/synyx/hera/si/PluginRegistryAwareMessageHandler.java +++ b/org.synyx.hera.si/src/main/java/org/synyx/hera/si/PluginRegistryAwareMessageHandler.java @@ -18,9 +18,14 @@ package org.synyx.hera.si; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.core.GenericTypeResolver; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; @@ -30,6 +35,8 @@ import org.springframework.integration.handler.AbstractReplyProducingMessageHand import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; +import org.synyx.hera.core.OrderAwarePluginRegistry; import org.synyx.hera.core.Plugin; import org.synyx.hera.core.PluginRegistry; @@ -41,19 +48,17 @@ import org.synyx.hera.core.PluginRegistry; */ public class PluginRegistryAwareMessageHandler extends AbstractReplyProducingMessageHandler { - private enum InvocationMethod { - ONE, ALL; - } + private static final Log LOG = LogFactory.getLog(PluginRegistryAwareMessageHandler.class); private final PluginRegistry, Object> registry; private final Class> pluginType; - private final Class delimitzerType; + private final Class delimiterType; private final SpelExpressionParser parser = new SpelExpressionParser(); private Expression delimiterExpression; private Expression invocationArgumentsExpression; private String serviceMethodName; - private InvocationMethod invocationMethod = InvocationMethod.ONE; + private PluginLookupMethod pluginLookupMethod = PluginLookupMethod.getDefault(); /** * Creates a new {@link PluginRegistryAwareMessageHandler} for the given {@link PluginRegistry}, pluginType and a @@ -74,7 +79,26 @@ public class PluginRegistryAwareMessageHandler extends AbstractReplyProducingMes this.registry = (PluginRegistry, Object>) registry; this.serviceMethodName = serviceMethodName; this.pluginType = pluginType; - this.delimitzerType = GenericTypeResolver.resolveTypeArgument(pluginType, Plugin.class); + this.delimiterType = GenericTypeResolver.resolveTypeArgument(pluginType, Plugin.class); + + verify(); + } + + private final void verify() { + + boolean methodFound = false; + + for (Method candidate : pluginType.getMethods()) { + if (candidate.getName().equals(serviceMethodName)) { + methodFound = true; + break; + } + } + + if (!methodFound) { + throw new IllegalArgumentException(String.format("Not method %s found for type %s!", serviceMethodName, + pluginType)); + } } /** @@ -98,6 +122,16 @@ public class PluginRegistryAwareMessageHandler extends AbstractReplyProducingMes this.invocationArgumentsExpression = parser.parseExpression(expression); } + /** + * Configures the method to be used when looking up plugins to invoke. + * + * @see PluginLookupMethod + * @param pluginLookupMethod the invocationMethod to set + */ + public void setPluginLookupMethod(PluginLookupMethod pluginLookupMethod) { + this.pluginLookupMethod = pluginLookupMethod == null ? PluginLookupMethod.getDefault() : pluginLookupMethod; + } + /* * (non-Javadoc) * @see org.springframework.integration.handler.AbstractReplyProducingMessageHandler#handleRequestMessage(org.springframework.integration.Message) @@ -108,18 +142,32 @@ public class PluginRegistryAwareMessageHandler extends AbstractReplyProducingMes Object delimiter = getDelimiter(requestMessage); - switch (invocationMethod) { + switch (pluginLookupMethod) { + case ALL: + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Looking up plugins for delimiter %s", delimiter)); + } return invokePlugins(registry.getPluginsFor(delimiter), requestMessage); + case ONE: - default: + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Looking up plugin for delimiter %s", delimiter)); + } List results = invokePlugins(Arrays.asList(registry.getPluginFor(delimiter)), requestMessage); return results.isEmpty() ? null : results.get(0); + + default: + throw new IllegalStateException(String.format("Unsupported plugin lookup method %s!", pluginLookupMethod)); } } - private List invokePlugins(Iterable> plugins, Message message) { + private List invokePlugins(Collection> plugins, Message message) { List results = new ArrayList(); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Invoking plugin(s) %s with message %s", + StringUtils.collectionToCommaDelimitedString(plugins), message)); + } for (Plugin plugin : plugins) { @@ -134,6 +182,11 @@ public class PluginRegistryAwareMessageHandler extends AbstractReplyProducingMes pluginType.getName(), Arrays.toString(types))); } + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Invoke plugin method %s using arguments %s", businessMethod, + Arrays.toString(invocationArguments))); + } + Object result = ReflectionUtils.invokeMethod(businessMethod, plugin, invocationArguments); if (!businessMethod.getReturnType().equals(void.class)) { @@ -160,9 +213,9 @@ public class PluginRegistryAwareMessageHandler extends AbstractReplyProducingMes delimiter = delimiterExpression.getValue(context); } - Assert.isInstanceOf(delimitzerType, delimiter, String.format("Delimiter expression did " + Assert.isInstanceOf(delimiterType, delimiter, String.format("Delimiter expression did " + "not return a suitable delimiter! Make sure the expression evaluates to a suitable " - + "type! Got %s but need %s", delimiter.getClass(), delimitzerType)); + + "type! Got %s but need %s", delimiter.getClass(), delimiterType)); return delimiter; } @@ -201,4 +254,36 @@ public class PluginRegistryAwareMessageHandler extends AbstractReplyProducingMes } return result; } + + + /** + * Lookup methods for plugins. + * + * @author Oliver Gierke + */ + private enum PluginLookupMethod { + + /** + * The first plugin supporting a given delimiter found will be invoked. + */ + ONE, + + /** + * All plugins supporting a given delimiter will be invoked. Plugin order will be considered. + * + * @see OrderAwarePluginRegistry + * @see Order + * @see Ordered + */ + ALL; + + /** + * Returns the default {@link PluginLookupMethod}. + * + * @return + */ + static PluginLookupMethod getDefault() { + return ONE; + } + } } diff --git a/org.synyx.hera.si/src/main/resources/org/synyx/hera/si/config/hera-si.xsd b/org.synyx.hera.si/src/main/resources/org/synyx/hera/si/config/hera-si.xsd index fc70ee8..2df6932 100644 --- a/org.synyx.hera.si/src/main/resources/org/synyx/hera/si/config/hera-si.xsd +++ b/org.synyx.hera.si/src/main/resources/org/synyx/hera/si/config/hera-si.xsd @@ -26,7 +26,7 @@ The input channel to listen to. - + The type of Spring beans to dynamically pick up. @@ -58,7 +58,33 @@ + + + + Defines whether to invoke the first plugin matching the delimiter + (default) or all found. Order of the plugins will be considered. + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.synyx.hera.si/src/test/java/org/synyx/hera/si/PluginRegistryAwareMessageHandlerUnitTest.java b/org.synyx.hera.si/src/test/java/org/synyx/hera/si/PluginRegistryAwareMessageHandlerUnitTest.java index adfcc00..f47c01c 100644 --- a/org.synyx.hera.si/src/test/java/org/synyx/hera/si/PluginRegistryAwareMessageHandlerUnitTest.java +++ b/org.synyx.hera.si/src/test/java/org/synyx/hera/si/PluginRegistryAwareMessageHandlerUnitTest.java @@ -14,11 +14,21 @@ * limitations under the License. */ package org.synyx.hera.si; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; import java.util.Arrays; +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; import org.springframework.integration.Message; +import org.springframework.integration.MessageChannel; import org.springframework.integration.MessageHandlingException; import org.springframework.integration.support.MessageBuilder; import org.synyx.hera.core.OrderAwarePluginRegistry; @@ -32,27 +42,52 @@ import org.synyx.hera.si.sample.SecondSamplePluginImpl; * * @author Oliver Gierke */ +@RunWith(MockitoJUnitRunner.class) public class PluginRegistryAwareMessageHandlerUnitTest { - PluginRegistry registry = OrderAwarePluginRegistry.create(Arrays.asList( - new FirstSamplePluginImpl(), new SecondSamplePluginImpl())); - PluginRegistryAwareMessageHandler handler = new PluginRegistryAwareMessageHandler(registry, SamplePlugin.class, - "myBusinessMethod"); + PluginRegistry registry; + PluginRegistryAwareMessageHandler handler; + + @Mock + MessageChannel outputChannel; + + @Before + public void setUp() { + registry = OrderAwarePluginRegistry + .create(Arrays.asList(new FirstSamplePluginImpl(), new SecondSamplePluginImpl())); + + handler = new PluginRegistryAwareMessageHandler(registry, SamplePlugin.class, "myBusinessMethod"); + handler.setOutputChannel(outputChannel); + } @Test + @SuppressWarnings("rawtypes") public void routesInvocationToFirstpluginIfConfiguredToDoSo() { handler.setDelimiterExpression("payload"); handler.setInvocationArgumentsExpression("payload"); + handler.afterPropertiesSet(); Message message = MessageBuilder.withPayload("FOO").build(); + when(outputChannel.send(Mockito.any(Message.class))).thenReturn(true); + handler.handleMessage(message); + + ArgumentCaptor resultMessage = ArgumentCaptor.forClass(Message.class); + verify(outputChannel).send(resultMessage.capture()); + assertThat(resultMessage.getValue().getPayload().toString(), is("First")); } - @Test(expected=MessageHandlingException.class) + @Test(expected = MessageHandlingException.class) public void failsHandlingMessageIfDelimiterTypeDoesNotMatch() { Message message = MessageBuilder.withPayload("FOO").build(); handler.handleMessage(message); } + + @Test(expected = IllegalArgumentException.class) + public void rejectsInvalidMethodName() { + + new PluginRegistryAwareMessageHandler(registry, SamplePlugin.class, "foo"); + } } diff --git a/org.synyx.hera.si/src/test/java/org/synyx/hera/si/sample/FirstSamplePluginImpl.java b/org.synyx.hera.si/src/test/java/org/synyx/hera/si/sample/FirstSamplePluginImpl.java index 1e562c5..6c71cb1 100644 --- a/org.synyx.hera.si/src/test/java/org/synyx/hera/si/sample/FirstSamplePluginImpl.java +++ b/org.synyx.hera.si/src/test/java/org/synyx/hera/si/sample/FirstSamplePluginImpl.java @@ -1,3 +1,18 @@ +/* + * Copyright 2011 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.synyx.hera.si.sample; /** @@ -16,7 +31,8 @@ public class FirstSamplePluginImpl implements SamplePlugin { /* (non-Javadoc) * @see org.synyx.hera.si.sample.SamplePlugin#myBusinessMethod() */ - public void myBusinessMethod(String message) { + public String myBusinessMethod(String message) { System.out.println("First plugin invoked! " + message); + return "First"; } } diff --git a/org.synyx.hera.si/src/test/java/org/synyx/hera/si/sample/SamplePlugin.java b/org.synyx.hera.si/src/test/java/org/synyx/hera/si/sample/SamplePlugin.java index 05a19d9..d30b1c8 100644 --- a/org.synyx.hera.si/src/test/java/org/synyx/hera/si/sample/SamplePlugin.java +++ b/org.synyx.hera.si/src/test/java/org/synyx/hera/si/sample/SamplePlugin.java @@ -1,3 +1,18 @@ +/* + * Copyright 2011 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.synyx.hera.si.sample; import org.synyx.hera.core.Plugin; @@ -8,5 +23,5 @@ import org.synyx.hera.core.Plugin; */ public interface SamplePlugin extends Plugin { - void myBusinessMethod(String message); + String myBusinessMethod(String message); } diff --git a/org.synyx.hera.si/src/test/java/org/synyx/hera/si/sample/SecondSamplePluginImpl.java b/org.synyx.hera.si/src/test/java/org/synyx/hera/si/sample/SecondSamplePluginImpl.java index 36c2d14..637abe8 100644 --- a/org.synyx.hera.si/src/test/java/org/synyx/hera/si/sample/SecondSamplePluginImpl.java +++ b/org.synyx.hera.si/src/test/java/org/synyx/hera/si/sample/SecondSamplePluginImpl.java @@ -1,9 +1,28 @@ +/* + * Copyright 2011 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.synyx.hera.si.sample; +import org.springframework.core.annotation.Order; + /** + * Sample implementation of {@link SamplePlugin} supporting {@code BAR} delimiter. * * @author Oliver Gierke */ +@Order(10) public class SecondSamplePluginImpl implements SamplePlugin { /* (non-Javadoc) @@ -16,7 +35,8 @@ public class SecondSamplePluginImpl implements SamplePlugin { /* (non-Javadoc) * @see org.synyx.hera.si.sample.SamplePlugin#myBusinessMethod() */ - public void myBusinessMethod(String message) { + public String myBusinessMethod(String message) { System.out.println("Second plugin invoked! " + message); + return "Second"; } } diff --git a/org.synyx.hera.si/src/test/java/org/synyx/hera/si/sample/ThirdSamplePluginImpl.java b/org.synyx.hera.si/src/test/java/org/synyx/hera/si/sample/ThirdSamplePluginImpl.java new file mode 100644 index 0000000..4a59d89 --- /dev/null +++ b/org.synyx.hera.si/src/test/java/org/synyx/hera/si/sample/ThirdSamplePluginImpl.java @@ -0,0 +1,42 @@ +/* + * Copyright 2011 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.synyx.hera.si.sample; + +import org.springframework.core.annotation.Order; + +/** + * Third sample implementation of {@link SamplePlugin} also supporting {@code BAR) but with lower precendence. + * + * @author Oliver Gierke + */ +@Order(20) +class ThirdSamplePluginImpl implements SamplePlugin { + + /* (non-Javadoc) + * @see org.synyx.hera.core.Plugin#supports(java.lang.Object) + */ + public boolean supports(String delimiter) { + return "BAR".equals(delimiter); + } + + /* (non-Javadoc) + * @see org.synyx.hera.si.sample.SamplePlugin#myBusinessMethod() + */ + public String myBusinessMethod(String message) { + System.out.println("Second plugin invoked! " + message); + return "Third"; + } +}