INT-2426: Add Idempotent Receiver EIP
JIRA: https://jira.spring.io/browse/INT-2426 INT-2426: pushed `final` modifier fix for Java 6 compatibility INT-2426: Rework logic to the `MetadataStore` INT-2426: `IdempotentReceiver` -> `IdempotentReceiverInterceptor` * Move `Idempotent Filtering` logic to the `IdempotentReceiverInterceptor`, which should be applied as a regular AOP `Advice` to the `MessageHandler#handleMessage` * Provide an xml component `<idempotent-receiver>` * Introduce `IdempotentReceiverAutoProxyCreator` to get deal with `IdempotentReceiverInterceptor` and `MessageHandler`s. The `Proxying` logic is based on the mapping between interceptor and `consumer endpoint` `ids` * Introduce `MetadataStoreSelector` along side with `MetadataKeyStrategy` and `ExpressionMetadataKeyStrategy` implementation INT-2426: Introduce `IdempotentReceiver` annotation Add `IdempotentReceiverIntegrationTests` in the JMX module to be sure that all proxying works well. INT-2426: Polishing according PR comments * Rename `IdempotentReceiverAutoProxyCreatorInitializer` * Add support for several `IRI` for the one `MH` * Polishing JavaDocs INT-2426: Add `What's New` note Doc Polishing. More Doc Polishing Use Timestamp (hex) instead of Id for Value Facilitate cleanup.
This commit is contained in:
committed by
Gary Russell
parent
6e81950236
commit
ff2b15ea9e
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans:beans xmlns="http://www.springframework.org/schema/integration"
|
||||
xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:util="http://www.springframework.org/schema/util"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration
|
||||
http://www.springframework.org/schema/integration/spring-integration.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
|
||||
|
||||
<beans:bean id="selector" class="org.springframework.integration.selector.PayloadTypeSelector">
|
||||
<beans:constructor-arg value="java.lang.String"/>
|
||||
</beans:bean>
|
||||
|
||||
<idempotent-receiver id="selectorInterceptor" endpoint="foo" selector="selector"/>
|
||||
|
||||
<beans:bean id="keyStrategy" class="org.mockito.Mockito" factory-method="mock">
|
||||
<beans:constructor-arg value="org.springframework.integration.metadata.MetadataKeyStrategy"/>
|
||||
</beans:bean>
|
||||
|
||||
<idempotent-receiver id="strategyInterceptor" endpoint="foo" key-strategy="keyStrategy"
|
||||
discard-channel="nullChannel" throw-exception-on-rejection="true"/>
|
||||
|
||||
<beans:bean id="store" class="org.springframework.integration.metadata.SimpleMetadataStore"/>
|
||||
|
||||
<util:properties id="properties">
|
||||
<beans:prop key="bar">bar*</beans:prop>
|
||||
</util:properties>
|
||||
|
||||
<context:property-placeholder properties-ref="properties"/>
|
||||
|
||||
<idempotent-receiver id="expressionInterceptor" endpoint="foo, ${bar}"
|
||||
metadata-store="store"
|
||||
key-expression="headers.foo"/>
|
||||
|
||||
</beans:beans>
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright 2014 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.config.xml;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.springframework.integration.test.util.TestUtils.getPropertyValue;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.PropertiesFactoryBean;
|
||||
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
|
||||
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.integration.core.MessageSelector;
|
||||
import org.springframework.integration.handler.advice.IdempotentReceiverInterceptor;
|
||||
import org.springframework.integration.metadata.ExpressionMetadataKeyStrategy;
|
||||
import org.springframework.integration.metadata.MetadataKeyStrategy;
|
||||
import org.springframework.integration.metadata.MetadataStore;
|
||||
import org.springframework.integration.selector.MetadataStoreSelector;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
* @since 4.1
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@DirtiesContext
|
||||
public class IdempotentReceiverParserTests {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("org.springframework.integration.config.IdempotentReceiverAutoProxyCreator")
|
||||
private BeanPostProcessor idempotentReceiverAutoProxyCreator;
|
||||
|
||||
@Autowired
|
||||
private IdempotentReceiverInterceptor selectorInterceptor;
|
||||
|
||||
@Autowired
|
||||
private MessageSelector selector;
|
||||
|
||||
@Autowired
|
||||
private IdempotentReceiverInterceptor strategyInterceptor;
|
||||
|
||||
@Autowired
|
||||
private MetadataKeyStrategy keyStrategy;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("nullChannel")
|
||||
private MessageChannel nullChannel;
|
||||
|
||||
@Autowired
|
||||
private IdempotentReceiverInterceptor expressionInterceptor;
|
||||
|
||||
@Autowired
|
||||
private MetadataStore store;
|
||||
|
||||
@Test
|
||||
public void testSelectorInterceptor() {
|
||||
assertSame(this.selector, getPropertyValue(this.selectorInterceptor, "messageSelector"));
|
||||
assertNull(getPropertyValue(this.selectorInterceptor, "discardChannel"));
|
||||
assertFalse(getPropertyValue(this.selectorInterceptor, "throwExceptionOnRejection", Boolean.class));
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, List<String>> idempotentEndpoints =
|
||||
(Map<String, List<String>>) getPropertyValue(this.idempotentReceiverAutoProxyCreator,
|
||||
"idempotentEndpoints", Map.class);
|
||||
List<String> endpoints = idempotentEndpoints.get("selectorInterceptor");
|
||||
assertNotNull(endpoints);
|
||||
assertFalse(endpoints.isEmpty());
|
||||
assertTrue(endpoints.contains("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStrategyInterceptor() {
|
||||
assertSame(this.nullChannel, getPropertyValue(this.strategyInterceptor, "discardChannel"));
|
||||
assertTrue(getPropertyValue(this.strategyInterceptor, "throwExceptionOnRejection", Boolean.class));
|
||||
Object messageSelector = getPropertyValue(this.strategyInterceptor, "messageSelector");
|
||||
assertThat(messageSelector, instanceOf(MetadataStoreSelector.class));
|
||||
assertSame(this.keyStrategy, getPropertyValue(messageSelector, "keyStrategy"));
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, List<String>> idempotentEndpoints =
|
||||
(Map<String, List<String>>) getPropertyValue(this.idempotentReceiverAutoProxyCreator,
|
||||
"idempotentEndpoints", Map.class);
|
||||
List<String> endpoints = idempotentEndpoints.get("strategyInterceptor");
|
||||
assertNotNull(endpoints);
|
||||
assertFalse(endpoints.isEmpty());
|
||||
assertTrue(endpoints.contains("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpressionInterceptor() {
|
||||
Object messageSelector = getPropertyValue(this.expressionInterceptor, "messageSelector");
|
||||
assertThat(messageSelector, instanceOf(MetadataStoreSelector.class));
|
||||
assertSame(this.store, getPropertyValue(messageSelector, "metadataStore"));
|
||||
Object keyStrategy = getPropertyValue(messageSelector, "keyStrategy");
|
||||
assertThat(keyStrategy, instanceOf(ExpressionMetadataKeyStrategy.class));
|
||||
assertThat(keyStrategy.toString(), containsString("headers.foo"));
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, List<String>> idempotentEndpoints =
|
||||
(Map<String, List<String>>) getPropertyValue(this.idempotentReceiverAutoProxyCreator,
|
||||
"idempotentEndpoints", Map.class);
|
||||
List<String> endpoints = idempotentEndpoints.get("expressionInterceptor");
|
||||
assertNotNull(endpoints);
|
||||
assertFalse(endpoints.isEmpty());
|
||||
assertTrue(endpoints.contains("foo"));
|
||||
assertTrue(endpoints.contains("bar*"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmpty() throws Exception {
|
||||
try {
|
||||
bootStrap("empty");
|
||||
fail("BeanDefinitionParsingException expected");
|
||||
}
|
||||
catch (BeanDefinitionParsingException e) {
|
||||
assertThat(e.getMessage(),
|
||||
containsString("One of the 'selector', 'key-strategy' or 'key-expression' attributes " +
|
||||
"must be provided"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithoutEndpoint() throws Exception {
|
||||
try {
|
||||
bootStrap("without-endpoint");
|
||||
fail("BeanDefinitionParsingException expected");
|
||||
}
|
||||
catch (BeanDefinitionParsingException e) {
|
||||
assertThat(e.getMessage(),
|
||||
containsString("he 'endpoint' attribute is required"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectorAndStore() throws Exception {
|
||||
try {
|
||||
bootStrap("selector-and-store");
|
||||
fail("BeanDefinitionParsingException expected");
|
||||
}
|
||||
catch (BeanDefinitionParsingException e) {
|
||||
assertThat(e.getMessage(),
|
||||
containsString("The 'selector' attribute is mutually exclusive with 'metadata-store', " +
|
||||
"'key-strategy' or 'key-expression'"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectorAndStrategy() throws Exception {
|
||||
try {
|
||||
bootStrap("selector-and-strategy");
|
||||
fail("BeanDefinitionParsingException expected");
|
||||
}
|
||||
catch (BeanDefinitionParsingException e) {
|
||||
assertThat(e.getMessage(),
|
||||
containsString("The 'selector' attribute is mutually exclusive with 'metadata-store', " +
|
||||
"'key-strategy' or 'key-expression'"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectorAndExpression() throws Exception {
|
||||
try {
|
||||
bootStrap("selector-and-expression");
|
||||
fail("BeanDefinitionParsingException expected");
|
||||
}
|
||||
catch (BeanDefinitionParsingException e) {
|
||||
assertThat(e.getMessage(),
|
||||
containsString("The 'selector' attribute is mutually exclusive with 'metadata-store', " +
|
||||
"'key-strategy' or 'key-expression'"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStrategyAndExpression() throws Exception {
|
||||
try {
|
||||
bootStrap("strategy-and-expression");
|
||||
fail("BeanDefinitionParsingException expected");
|
||||
}
|
||||
catch (BeanDefinitionParsingException e) {
|
||||
assertThat(e.getMessage(),
|
||||
containsString("The 'key-strategy' and 'key-expression' attributes are mutually exclusive"));
|
||||
}
|
||||
}
|
||||
|
||||
private ApplicationContext bootStrap(String configProperty) throws Exception {
|
||||
PropertiesFactoryBean pfb = new PropertiesFactoryBean();
|
||||
pfb.setLocation(new ClassPathResource(
|
||||
"org/springframework/integration/config/xml/idempotent-receiver-configs.properties"));
|
||||
pfb.afterPropertiesSet();
|
||||
Properties prop = pfb.getObject();
|
||||
ByteArrayInputStream stream = new ByteArrayInputStream((prop.getProperty("xmlheaders")
|
||||
+ prop.getProperty(configProperty) + prop.getProperty("xmlfooter")).getBytes());
|
||||
GenericApplicationContext ac = new GenericApplicationContext();
|
||||
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(ac);
|
||||
reader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_XSD);
|
||||
reader.loadBeanDefinitions(new InputStreamResource(stream));
|
||||
ac.refresh();
|
||||
return ac;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
xmlheaders=\
|
||||
<?xml version="1.0" encoding="UTF-8"?> \
|
||||
<beans xmlns="http://www.springframework.org/schema/beans" \
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" \
|
||||
xmlns:int="http://www.springframework.org/schema/integration" \
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd \
|
||||
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
|
||||
xmlfooter= </beans>
|
||||
|
||||
empty=<int:idempotent-receiver endpoint=""/>
|
||||
|
||||
without-endpoint=<int:idempotent-receiver endpoint="" selector="selector"/>
|
||||
|
||||
selector-and-store=<int:idempotent-receiver endpoint="foo" selector="selector" metadata-store="store"/>
|
||||
|
||||
selector-and-strategy=<int:idempotent-receiver endpoint="foo" selector="selector" key-strategy="strategy"/>
|
||||
|
||||
selector-and-expression=<int:idempotent-receiver endpoint="foo" selector="selector" key-expression="expression"/>
|
||||
|
||||
strategy-and-expression=<int:idempotent-receiver endpoint="foo" key-strategy="strategy" key-expression="expression"/>
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans:beans xmlns="http://www.springframework.org/schema/integration"
|
||||
xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration
|
||||
http://www.springframework.org/schema/integration/spring-integration.xsd">
|
||||
|
||||
<beans:bean id="advice" class="org.springframework.integration.handler.advice.IdempotentReceiverTests$FooAdvice"/>
|
||||
|
||||
<beans:bean id="store" class="org.springframework.integration.metadata.SimpleMetadataStore"/>
|
||||
|
||||
<beans:bean id="store2" class="org.springframework.integration.metadata.SimpleMetadataStore"/>
|
||||
|
||||
<idempotent-receiver id="idempotentReceiverInterceptor"
|
||||
endpoint="my*, output"
|
||||
metadata-store="store"
|
||||
key-expression="payload"
|
||||
throw-exception-on-rejection="true"/>
|
||||
|
||||
<idempotent-receiver id="idempotentReceiverInterceptor2"
|
||||
endpoint="myService2"
|
||||
metadata-store="store2"
|
||||
key-expression="payload.toUpperCase()"/>
|
||||
|
||||
<channel id="output">
|
||||
<queue/>
|
||||
</channel>
|
||||
|
||||
<service-activator id="myService" input-channel="input" output-channel="output" expression="payload">
|
||||
<request-handler-advice-chain>
|
||||
<ref bean="advice"/>
|
||||
</request-handler-advice-chain>
|
||||
</service-activator>
|
||||
|
||||
<service-activator id="myService2" input-channel="input2" output-channel="output" expression="payload"/>
|
||||
|
||||
</beans:beans>
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright 2014 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 static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.integration.IntegrationMessageHeaderAccessor;
|
||||
import org.springframework.integration.MessageRejectedException;
|
||||
import org.springframework.integration.metadata.ConcurrentMetadataStore;
|
||||
import org.springframework.integration.metadata.ExpressionMetadataKeyStrategy;
|
||||
import org.springframework.integration.metadata.MetadataStore;
|
||||
import org.springframework.integration.metadata.SimpleMetadataStore;
|
||||
import org.springframework.integration.selector.MetadataStoreSelector;
|
||||
import org.springframework.integration.support.DefaultMessageBuilderFactory;
|
||||
import org.springframework.integration.support.MessageBuilderFactory;
|
||||
import org.springframework.integration.support.utils.IntegrationUtils;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
* @since 4.1
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@DirtiesContext
|
||||
public class IdempotentReceiverTests {
|
||||
|
||||
@Autowired
|
||||
private MessageChannel input;
|
||||
|
||||
@Autowired
|
||||
private MessageChannel input2;
|
||||
|
||||
@Autowired
|
||||
private PollableChannel output;
|
||||
|
||||
@Autowired
|
||||
private MetadataStore store;
|
||||
|
||||
@Autowired
|
||||
private MetadataStore store2;
|
||||
|
||||
@Autowired
|
||||
private IdempotentReceiverInterceptor idempotentReceiverInterceptor;
|
||||
|
||||
@Autowired
|
||||
private FooAdvice fooAdvice;
|
||||
|
||||
@Test
|
||||
public void testIdempotentReceiverInterceptor() {
|
||||
ConcurrentMetadataStore store = new SimpleMetadataStore();
|
||||
ExpressionMetadataKeyStrategy idempotentKeyStrategy = new ExpressionMetadataKeyStrategy("payload");
|
||||
BeanFactory beanFactory = Mockito.mock(BeanFactory.class);
|
||||
idempotentKeyStrategy.setBeanFactory(beanFactory);
|
||||
IdempotentReceiverInterceptor idempotentReceiverInterceptor =
|
||||
new IdempotentReceiverInterceptor(new MetadataStoreSelector(idempotentKeyStrategy, store));
|
||||
idempotentReceiverInterceptor.setThrowExceptionOnRejection(true);
|
||||
|
||||
AtomicReference<Message<?>> handled = new AtomicReference<>();
|
||||
|
||||
MessageHandler idempotentReceiver = handled::set;
|
||||
|
||||
ProxyFactory proxyFactory = new ProxyFactory(idempotentReceiver);
|
||||
proxyFactory.addAdvice(idempotentReceiverInterceptor);
|
||||
idempotentReceiver = (MessageHandler) proxyFactory.getProxy();
|
||||
|
||||
idempotentReceiver.handleMessage(new GenericMessage<>("foo"));
|
||||
assertEquals(1, TestUtils.getPropertyValue(store, "metadata", Map.class).size());
|
||||
assertNotNull(store.get("foo"));
|
||||
|
||||
try {
|
||||
idempotentReceiver.handleMessage(new GenericMessage<>("foo"));
|
||||
fail("MessageRejectedException expected");
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertThat(e, instanceOf(MessageRejectedException.class));
|
||||
}
|
||||
|
||||
idempotentReceiverInterceptor.setThrowExceptionOnRejection(false);
|
||||
idempotentReceiver.handleMessage(new GenericMessage<>("foo"));
|
||||
assertTrue(handled.get().getHeaders().get(IntegrationMessageHeaderAccessor.DUPLICATE_MESSAGE,
|
||||
Boolean.class));
|
||||
assertEquals(1, TestUtils.getPropertyValue(store, "metadata", Map.class).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdempotentReceiver() {
|
||||
Message<String> message = new GenericMessage<>("foo");
|
||||
this.input.send(message);
|
||||
Message<?> receive = this.output.receive(10000);
|
||||
assertNotNull(receive);
|
||||
assertEquals(1, this.fooAdvice.adviceCalled);
|
||||
assertEquals(1, TestUtils.getPropertyValue(this.store, "metadata", Map.class).size());
|
||||
assertNotNull(this.store.get("foo"));
|
||||
|
||||
try {
|
||||
this.input.send(message);
|
||||
fail("MessageRejectedException expected");
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertThat(e, instanceOf(MessageRejectedException.class));
|
||||
}
|
||||
this.idempotentReceiverInterceptor.setThrowExceptionOnRejection(false);
|
||||
this.input.send(message);
|
||||
receive = this.output.receive(10000);
|
||||
assertNotNull(receive);
|
||||
assertEquals(2, this.fooAdvice.adviceCalled);
|
||||
assertTrue(receive.getHeaders().get(IntegrationMessageHeaderAccessor.DUPLICATE_MESSAGE, Boolean.class));
|
||||
assertEquals(1, TestUtils.getPropertyValue(this.store, "metadata", Map.class).size());
|
||||
|
||||
message = new GenericMessage<>("bar");
|
||||
for (int i = 0; i < 2; i++) {
|
||||
this.input2.send(message);
|
||||
receive = this.output.receive(10000);
|
||||
assertNotNull(receive);
|
||||
}
|
||||
|
||||
assertTrue(receive.getHeaders().get(IntegrationMessageHeaderAccessor.DUPLICATE_MESSAGE, Boolean.class));
|
||||
assertEquals(2, TestUtils.getPropertyValue(this.store, "metadata", Map.class).size());
|
||||
assertNotNull(this.store.get("bar"));
|
||||
assertEquals(1, TestUtils.getPropertyValue(this.store2, "metadata", Map.class).size());
|
||||
assertNotNull(this.store2.get("BAR"));
|
||||
}
|
||||
|
||||
public static class FooAdvice extends AbstractRequestHandlerAdvice {
|
||||
|
||||
private int adviceCalled;
|
||||
|
||||
@Override
|
||||
protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception {
|
||||
adviceCalled++;
|
||||
return callback.execute();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user