#510 - Further improvements to PluginRegistryAwareMessageHandler.
Fixed typo in property name. Added unit test to reject invalid method name on construction already. Added logging output in debug log level. Tightened XSD to hint plugin-type attribute to be a class.
This commit is contained in:
@@ -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<? extends Plugin<?>, Object> registry;
|
||||
private final Class<? extends Plugin<?>> 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<? extends Plugin<?>, 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<Object> 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<Object> invokePlugins(Iterable<? extends Plugin<?>> plugins, Message<?> message) {
|
||||
private List<Object> invokePlugins(Collection<? extends Plugin<?>> plugins, Message<?> message) {
|
||||
List<Object> results = new ArrayList<Object>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<xsd:documentation>The input channel to listen to.</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="plugin-type" type="xsd:string" use="required">
|
||||
<xsd:attribute name="plugin-type" type="classType" use="required">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>The type of Spring beans to dynamically pick up.</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
@@ -58,7 +58,33 @@
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="plugin-lookup-method" type="pluginLookupMethod" default="one">
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
Defines whether to invoke the first plugin matching the delimiter
|
||||
(default) or all found. Order of the plugins will be considered.
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
|
||||
<xsd:simpleType name="pluginLookupMethod">
|
||||
<xsd:restriction base="xsd:string">
|
||||
<xsd:enumeration value="one" />
|
||||
<xsd:enumeration value="all" />
|
||||
</xsd:restriction>
|
||||
</xsd:simpleType>
|
||||
|
||||
<xsd:simpleType name="classType">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<tool:annotation kind="direct">
|
||||
<tool:expected-type type="java.lang.Class" />
|
||||
</tool:annotation>
|
||||
</xsd:appinfo>
|
||||
</xsd:annotation>
|
||||
<xsd:union memberTypes="xsd:string" />
|
||||
</xsd:simpleType>
|
||||
|
||||
</xsd:schema>
|
||||
@@ -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<SamplePlugin, String> registry = OrderAwarePluginRegistry.create(Arrays.asList(
|
||||
new FirstSamplePluginImpl(), new SecondSamplePluginImpl()));
|
||||
PluginRegistryAwareMessageHandler handler = new PluginRegistryAwareMessageHandler(registry, SamplePlugin.class,
|
||||
"myBusinessMethod");
|
||||
PluginRegistry<SamplePlugin, String> 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<String> message = MessageBuilder.withPayload("FOO").build();
|
||||
when(outputChannel.send(Mockito.any(Message.class))).thenReturn(true);
|
||||
|
||||
handler.handleMessage(message);
|
||||
|
||||
ArgumentCaptor<Message> 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<String> message = MessageBuilder.withPayload("FOO").build();
|
||||
handler.handleMessage(message);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsInvalidMethodName() {
|
||||
|
||||
new PluginRegistryAwareMessageHandler(registry, SamplePlugin.class, "foo");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> {
|
||||
|
||||
void myBusinessMethod(String message);
|
||||
String myBusinessMethod(String message);
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user