INT-3500: Consider to add "error-channel" to the <enricher>

JIRA: https://jira.spring.io/browse/INT-3500

* add 'error-channel' to the <enricher> component
* delegate errorChannel to the internal gateway in ContentEnricher
* added tests for xml based and java based enrichers with error channels

INT-3500: Consider to add "error-channel" to the <enricher>

* Fixed typos and comments
* Reworked xml integration test to use the outputChannel and increased timeout
* Added overview in whats-new doc

INT-3500: Consider to add "error-channel" to the <enricher>

* Ensure requestChannel is set if an errorChannel is set
* Increased timeout to fix unit test
* Added details to content-enricher ref doc

Polishing
This commit is contained in:
Kris Jacyna
2014-09-15 14:46:13 +03:00
committed by Artem Bilan
parent 17b91592fb
commit 30b58836e5
8 changed files with 291 additions and 42 deletions

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
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">
<channel id="inputChannel"/>
<channel id="requestChannel"/>
<channel id="errChannel"/>
<channel id="outputChannel">
<queue />
</channel>
<enricher id="enricher"
input-channel="inputChannel" request-channel="requestChannel"
output-channel="outputChannel" error-channel="errChannel">
<property name="name" expression="'Mr. ' + payload.name"/>
</enricher>
</beans:beans>

View File

@@ -0,0 +1,97 @@
/*
* 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.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.handler.AbstractReplyProducingMessageHandler;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
import org.springframework.messaging.PollableChannel;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Tests the error-channel in an enricher to produce
* a default object in case of downstream failure.
*
* @author Kris Jacyna
* @since 4.1
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class EnricherParserTests5 {
@Autowired
private ApplicationContext context;
@Test
public void errorChannelTest() {
class ErrorThrower extends AbstractReplyProducingMessageHandler {
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
throw new RuntimeException();
}
}
class DefaultTargetProducer extends AbstractReplyProducingMessageHandler {
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
final Target defaultTarget = new Target();
defaultTarget.setName("Default");
return defaultTarget;
}
}
context.getBean("requestChannel", DirectChannel.class).subscribe(new ErrorThrower());
context.getBean("errChannel", DirectChannel.class).subscribe(new DefaultTargetProducer());
Target original = new Target();
original.setName("John");
Message<?> request = MessageBuilder.withPayload(original).build();
context.getBean("inputChannel", DirectChannel.class).send(request);
Message<?> reply = context.getBean("outputChannel", PollableChannel.class).receive(10000);
Target enriched = (Target) reply.getPayload();
assertEquals("Mr. Default", enriched.getName());
}
public static class Target {
private volatile String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}

View File

@@ -57,6 +57,7 @@ import org.springframework.scheduling.support.PeriodicTrigger;
* @author Gunnar Hillert
* @author Artem Bilan
* @author Gary Russell
* @author Kris Jacyna
*
* @since 2.1
*/
@@ -85,9 +86,9 @@ public class ContentEnricherTests {
public void replyChannelReplyTimingOut() throws Exception {
final long requestTimeout = 500L;
final long replyTimeout = 700L;
final long replyTimeout = 700L;
final DirectChannel replyChannel = new DirectChannel();
final DirectChannel replyChannel = new DirectChannel();
final QueueChannel requestChannel = new QueueChannel(1);
final ContentEnricher enricher = new ContentEnricher();
@@ -144,10 +145,11 @@ public class ContentEnricherTests {
Message<?> requestMessage = MessageBuilder.withPayload(target).setReplyChannel(replyChannel).build();
try {
enricher.handleMessage(requestMessage);
enricher.handleMessage(requestMessage);
}
catch (ReplyRequiredException e) {
assertEquals("No reply produced by handler 'Enricher', and its 'requiresReply' property is set to true.", e.getMessage());
assertEquals("No reply produced by handler 'Enricher', and its 'requiresReply' property is set to true.",
e.getMessage());
return;
}
@@ -161,7 +163,7 @@ public class ContentEnricherTests {
final String requestChannelName = "Request_Channel";
final long requestTimeout = 200L;
QueueChannel replyChannel = new QueueChannel();
QueueChannel replyChannel = new QueueChannel();
QueueChannel requestChannel = new RendezvousChannel();
requestChannel.setBeanName(requestChannelName);
@@ -179,8 +181,7 @@ public class ContentEnricherTests {
}
catch (MessageDeliveryException e) {
assertEquals("failed to send message to channel '" + requestChannelName
+ "' within timeout: " + requestTimeout, e.getMessage());
return;
+ "' within timeout: " + requestTimeout, e.getMessage());
}
}
@@ -216,7 +217,7 @@ public class ContentEnricherTests {
@Test
public void setReplyChannelWithoutRequestChannel() {
QueueChannel replyChannel = new QueueChannel();
QueueChannel replyChannel = new QueueChannel();
ContentEnricher enricher = new ContentEnricher();
enricher.setReplyChannel(replyChannel);
@@ -287,8 +288,8 @@ public class ContentEnricherTests {
@Test
public void testContentEnricherWithNullRequestChannel() {
ContentEnricher enricher = new ContentEnricher();
enricher.setReplyChannel(new QueueChannel());
ContentEnricher enricher = new ContentEnricher();
enricher.setReplyChannel(new QueueChannel());
enricher.setBeanFactory(mock(BeanFactory.class));
try {
@@ -383,7 +384,7 @@ public class ContentEnricherTests {
enricher.afterPropertiesSet();
TargetUser target = new TargetUser();
target.setName("replace me");
target.setName("replace me");
Message<?> requestMessage = MessageBuilder.withPayload(target).setReplyChannel(replyChannel).build();
enricher.handleMessage(requestMessage);
@@ -416,12 +417,12 @@ public class ContentEnricherTests {
enricher.afterPropertiesSet();
UncloneableTargetUser target = new UncloneableTargetUser();
target.setName("replace me");
target.setName("replace me");
Message<?> requestMessage = MessageBuilder.withPayload(target).setReplyChannel(replyChannel).build();
try {
enricher.handleMessage(requestMessage);
enricher.handleMessage(requestMessage);
}
catch (MessageHandlingException e) {
assertThat(e.getMessage(), containsString("Failed to clone payload object"));
@@ -469,6 +470,56 @@ public class ContentEnricherTests {
assertTrue(enricher.isRunning());
}
/**
* In this test a {@link Target} message is passed into a {@link ContentEnricher}.
* The Enricher passes the message to a "request-channel" to a handler which throws
* an exception. The {@link ContentEnricher} then uses the error flow and consults
* the "error-channel" which returns a alternative {@link Target}.
*/
@Test
public void testErrorChannel() throws Exception {
final DirectChannel requestChannel = new DirectChannel();
requestChannel.subscribe(new AbstractReplyProducingMessageHandler() {
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
throw new RuntimeException();
}
});
final DirectChannel errorChannel = new DirectChannel();
errorChannel.subscribe(new AbstractReplyProducingMessageHandler() {
@Override
protected Object handleRequestMessage(Message<?> requestMessage) {
return new Target("failed");
}
});
final QueueChannel replyChannel = new QueueChannel();
final ContentEnricher enricher = new ContentEnricher();
enricher.setRequestChannel(requestChannel);
enricher.setErrorChannel(errorChannel);
SpelExpressionParser parser = new SpelExpressionParser();
Map<String, Expression> propertyExpressions = new HashMap<String, Expression>();
propertyExpressions.put("name", parser.parseExpression("payload.name + ' target'"));
enricher.setPropertyExpressions(propertyExpressions);
enricher.setBeanFactory(mock(BeanFactory.class));
enricher.afterPropertiesSet();
final Target target = new Target("replace me");
Message<?> requestMessage = MessageBuilder.withPayload(target).setReplyChannel(replyChannel).build();
enricher.handleMessage(requestMessage);
Message<?> reply = replyChannel.receive(10000);
Target result = (Target) reply.getPayload();
assertEquals("failed target", result.getName());
}
@SuppressWarnings("unused")
private static final class Source {