/* * Copyright 2002-2016 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.transformer; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalToIgnoringCase; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; 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.mockito.Mockito.mock; import java.util.HashMap; import java.util.Map; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.BeanFactory; import org.springframework.expression.Expression; import org.springframework.expression.common.LiteralExpression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.channel.NullChannel; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.channel.RendezvousChannel; import org.springframework.integration.config.ExpressionFactoryBean; import org.springframework.integration.config.TestErrorHandler; import org.springframework.integration.endpoint.PollingConsumer; import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; import org.springframework.integration.handler.ReplyRequiredException; import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.Message; import org.springframework.messaging.MessageDeliveryException; import org.springframework.messaging.MessageHandlingException; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.support.PeriodicTrigger; /** * @author Mark Fisher * @author Gunnar Hillert * @author Artem Bilan * @author Gary Russell * @author Kris Jacyna * * @since 2.1 */ public class ContentEnricherTests { private final ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); @Before public void init() throws Exception { taskScheduler.setPoolSize(2); taskScheduler.afterPropertiesSet(); } /** * In this test a {@link Target} message is passed into a {@link ContentEnricher}. * The Enricher passes the message to a "request-channel" that is backed by a * {@link QueueChannel}. The consumer of the "request-channel" takes a long * time to execute, longer actually than the specified "replyTimeout" set on * the {@link ContentEnricher}. * * Due to the occurring replyTimeout, a Null replyMessage is returned and because * "requiresReply" is set to "true" on the {@link ContentEnricher}, a * {@link ReplyRequiredException} is raised. */ @Test public void replyChannelReplyTimingOut() throws Exception { final long requestTimeout = 500L; final long replyTimeout = 700L; final DirectChannel replyChannel = new DirectChannel(); final QueueChannel requestChannel = new QueueChannel(1); final ContentEnricher enricher = new ContentEnricher(); enricher.setRequestChannel(requestChannel); enricher.setReplyChannel(replyChannel); enricher.setOutputChannel(new NullChannel()); enricher.setRequestTimeout(requestTimeout); enricher.setReplyTimeout(replyTimeout); final ExpressionFactoryBean expressionFactoryBean = new ExpressionFactoryBean("payload"); expressionFactoryBean.setSingleton(false); expressionFactoryBean.afterPropertiesSet(); final Map<String, Expression> expressions = new HashMap<String, Expression>(); expressions.put("name", new LiteralExpression("cartman")); expressions.put("child.name", expressionFactoryBean.getObject()); enricher.setPropertyExpressions(expressions); enricher.setRequiresReply(true); enricher.setBeanName("Enricher"); enricher.setBeanFactory(mock(BeanFactory.class)); enricher.afterPropertiesSet(); final AbstractReplyProducingMessageHandler handler = new AbstractReplyProducingMessageHandler() { @Override protected Object handleRequestMessage(Message<?> requestMessage) { try { Thread.sleep(5000); } catch (InterruptedException e) { fail(e.getMessage()); } return new Target("child"); } }; handler.setBeanFactory(mock(BeanFactory.class)); handler.afterPropertiesSet(); final PollingConsumer consumer = new PollingConsumer(requestChannel, handler); final TestErrorHandler errorHandler = new TestErrorHandler(); consumer.setTrigger(new PeriodicTrigger(0)); consumer.setErrorHandler(errorHandler); consumer.setTaskScheduler(taskScheduler); consumer.setBeanFactory(mock(BeanFactory.class)); consumer.afterPropertiesSet(); consumer.start(); final Target target = new Target("replace me"); Message<?> requestMessage = MessageBuilder.withPayload(target).setReplyChannel(replyChannel).build(); try { enricher.handleMessage(requestMessage); } catch (ReplyRequiredException e) { assertEquals("No reply produced by handler 'Enricher', and its 'requiresReply' property is set to true.", e.getMessage()); return; } fail("ReplyRequiredException expected."); } @Test public void requestChannelSendTimingOut() { final String requestChannelName = "Request_Channel"; final long requestTimeout = 200L; QueueChannel replyChannel = new QueueChannel(); QueueChannel requestChannel = new RendezvousChannel(); requestChannel.setBeanName(requestChannelName); ContentEnricher enricher = new ContentEnricher(); enricher.setRequestChannel(requestChannel); enricher.setRequestTimeout(requestTimeout); enricher.setBeanFactory(mock(BeanFactory.class)); enricher.afterPropertiesSet(); Target target = new Target("replace me"); Message<?> requestMessage = MessageBuilder.withPayload(target).setReplyChannel(replyChannel).build(); try { enricher.handleMessage(requestMessage); } catch (MessageDeliveryException e) { assertThat(e.getMessage(), equalToIgnoringCase("failed to send message to channel '" + requestChannelName + "' within timeout: " + requestTimeout)); } } @Test public void simpleProperty() { QueueChannel replyChannel = new QueueChannel(); DirectChannel requestChannel = new DirectChannel(); requestChannel.subscribe(new AbstractReplyProducingMessageHandler() { @Override protected Object handleRequestMessage(Message<?> requestMessage) { return new Source("John", "Doe"); } }); ContentEnricher enricher = new ContentEnricher(); enricher.setRequestChannel(requestChannel); SpelExpressionParser parser = new SpelExpressionParser(); Map<String, Expression> propertyExpressions = new HashMap<String, Expression>(); propertyExpressions.put("name", parser.parseExpression("payload.lastName + ', ' + payload.firstName")); enricher.setPropertyExpressions(propertyExpressions); enricher.setBeanFactory(mock(BeanFactory.class)); enricher.afterPropertiesSet(); Target target = new Target("replace me"); Message<?> requestMessage = MessageBuilder.withPayload(target).setReplyChannel(replyChannel).build(); enricher.handleMessage(requestMessage); Message<?> reply = replyChannel.receive(0); assertEquals("Doe, John", ((Target) reply.getPayload()).getName()); } @Test public void setReplyChannelWithoutRequestChannel() { QueueChannel replyChannel = new QueueChannel(); ContentEnricher enricher = new ContentEnricher(); enricher.setReplyChannel(replyChannel); enricher.setBeanFactory(mock(BeanFactory.class)); try { enricher.afterPropertiesSet(); } catch (IllegalStateException e) { assertEquals("If the replyChannel is set, then the requestChannel must not be null", e.getMessage()); return; } fail("Expected an exception."); } @Test public void setNullReplyTimeout() { ContentEnricher enricher = new ContentEnricher(); enricher.setBeanFactory(mock(BeanFactory.class)); try { enricher.setReplyTimeout(null); } catch (IllegalArgumentException e) { assertEquals("replyTimeout must not be null", e.getMessage()); return; } fail("Expected an exception."); } @Test public void setNullRequestTimeout() { ContentEnricher enricher = new ContentEnricher(); enricher.setBeanFactory(mock(BeanFactory.class)); try { enricher.setRequestTimeout(null); } catch (IllegalArgumentException e) { assertEquals("requestTimeout must not be null", e.getMessage()); return; } fail("Expected an exception."); } @Test public void testSimplePropertyWithoutUsingRequestChannel() { QueueChannel replyChannel = new QueueChannel(); ContentEnricher enricher = new ContentEnricher(); SpelExpressionParser parser = new SpelExpressionParser(); Map<String, Expression> propertyExpressions = new HashMap<String, Expression>(); propertyExpressions.put("name", parser.parseExpression("'just a static string'")); enricher.setPropertyExpressions(propertyExpressions); enricher.setBeanFactory(mock(BeanFactory.class)); enricher.afterPropertiesSet(); Target target = new Target("replace me"); Message<?> requestMessage = MessageBuilder.withPayload(target).setReplyChannel(replyChannel).build(); enricher.handleMessage(requestMessage); Message<?> reply = replyChannel.receive(0); assertEquals("just a static string", ((Target) reply.getPayload()).getName()); } @Test public void testContentEnricherWithNullRequestChannel() { ContentEnricher enricher = new ContentEnricher(); enricher.setReplyChannel(new QueueChannel()); enricher.setBeanFactory(mock(BeanFactory.class)); try { enricher.afterPropertiesSet(); } catch (IllegalStateException e) { assertEquals("If the replyChannel is set, then the requestChannel must not be null", e.getMessage()); return; } fail("Expected an IllegalArgumentException to be thrown."); } @Test public void nestedProperty() { QueueChannel replyChannel = new QueueChannel(); DirectChannel requestChannel = new DirectChannel(); requestChannel.subscribe(new AbstractReplyProducingMessageHandler() { @Override protected Object handleRequestMessage(Message<?> requestMessage) { return new Source("John", "Doe"); } }); ContentEnricher enricher = new ContentEnricher(); enricher.setRequestChannel(requestChannel); SpelExpressionParser parser = new SpelExpressionParser(); Map<String, Expression> propertyExpressions = new HashMap<String, Expression>(); propertyExpressions.put("child.name", parser.parseExpression("payload.lastName + ', ' + payload.firstName")); enricher.setPropertyExpressions(propertyExpressions); enricher.setBeanFactory(mock(BeanFactory.class)); enricher.afterPropertiesSet(); Target target = new Target("test"); Message<?> requestMessage = MessageBuilder.withPayload(target).setReplyChannel(replyChannel).build(); enricher.handleMessage(requestMessage); Message<?> reply = replyChannel.receive(0); Target result = (Target) reply.getPayload(); assertEquals("test", result.getName()); assertEquals("Doe, John", result.getChild().getName()); } @Test public void clonePayload() { QueueChannel replyChannel = new QueueChannel(); DirectChannel requestChannel = new DirectChannel(); requestChannel.subscribe(new AbstractReplyProducingMessageHandler() { @Override protected Object handleRequestMessage(Message<?> requestMessage) { return new Source("John", "Doe"); } }); ContentEnricher enricher = new ContentEnricher(); enricher.setRequestChannel(requestChannel); enricher.setShouldClonePayload(true); SpelExpressionParser parser = new SpelExpressionParser(); Map<String, Expression> propertyExpressions = new HashMap<String, Expression>(); propertyExpressions.put("name", parser.parseExpression("payload.lastName + ', ' + payload.firstName")); enricher.setPropertyExpressions(propertyExpressions); enricher.setBeanFactory(mock(BeanFactory.class)); enricher.afterPropertiesSet(); Target target = new Target("replace me"); Message<?> requestMessage = MessageBuilder.withPayload(target).setReplyChannel(replyChannel).build(); enricher.handleMessage(requestMessage); Message<?> reply = replyChannel.receive(0); Target result = (Target) reply.getPayload(); assertEquals("Doe, John", result.getName()); assertNotSame(target, result); } @Test public void clonePayloadIgnored() { QueueChannel replyChannel = new QueueChannel(); DirectChannel requestChannel = new DirectChannel(); requestChannel.subscribe(new AbstractReplyProducingMessageHandler() { @Override protected Object handleRequestMessage(Message<?> requestMessage) { return new Source("John", "Doe"); } }); ContentEnricher enricher = new ContentEnricher(); enricher.setRequestChannel(requestChannel); enricher.setShouldClonePayload(true); SpelExpressionParser parser = new SpelExpressionParser(); Map<String, Expression> propertyExpressions = new HashMap<String, Expression>(); propertyExpressions.put("name", parser.parseExpression("payload.lastName + ', ' + payload.firstName")); enricher.setPropertyExpressions(propertyExpressions); enricher.setBeanFactory(mock(BeanFactory.class)); enricher.afterPropertiesSet(); TargetUser target = new TargetUser(); target.setName("replace me"); Message<?> requestMessage = MessageBuilder.withPayload(target).setReplyChannel(replyChannel).build(); enricher.handleMessage(requestMessage); Message<?> reply = replyChannel.receive(0); TargetUser result = (TargetUser) reply.getPayload(); assertEquals("Doe, John", result.getName()); assertSame(target, result); } @Test public void clonePayloadWithFailure() { QueueChannel replyChannel = new QueueChannel(); DirectChannel requestChannel = new DirectChannel(); requestChannel.subscribe(new AbstractReplyProducingMessageHandler() { @Override protected Object handleRequestMessage(Message<?> requestMessage) { return new Source("John", "Doe"); } }); ContentEnricher enricher = new ContentEnricher(); enricher.setRequestChannel(requestChannel); enricher.setShouldClonePayload(true); SpelExpressionParser parser = new SpelExpressionParser(); Map<String, Expression> propertyExpressions = new HashMap<String, Expression>(); propertyExpressions.put("name", parser.parseExpression("payload.lastName + ', ' + payload.firstName")); enricher.setPropertyExpressions(propertyExpressions); enricher.setBeanFactory(mock(BeanFactory.class)); enricher.afterPropertiesSet(); UncloneableTargetUser target = new UncloneableTargetUser(); target.setName("replace me"); Message<?> requestMessage = MessageBuilder.withPayload(target).setReplyChannel(replyChannel).build(); try { enricher.handleMessage(requestMessage); } catch (MessageHandlingException e) { assertThat(e.getMessage(), containsString("Failed to clone payload object")); return; } fail("Expected a MessageHandlingException to be thrown."); } @Test public void testLifeCycleMethodsWithoutRequestChannel() { ContentEnricher enricher = new ContentEnricher(); enricher.setBeanFactory(mock(BeanFactory.class)); enricher.afterPropertiesSet(); assertTrue(enricher.isRunning()); enricher.stop(); assertTrue(enricher.isRunning()); } @Test public void testLifeCycleMethodsWithRequestChannel() { DirectChannel requestChannel = new DirectChannel(); requestChannel.subscribe(new AbstractReplyProducingMessageHandler() { @Override protected Object handleRequestMessage(Message<?> requestMessage) { return new Source("John", "Doe"); } }); ContentEnricher enricher = new ContentEnricher(); enricher.setRequestChannel(requestChannel); enricher.setBeanFactory(mock(BeanFactory.class)); enricher.afterPropertiesSet(); enricher.start(); assertTrue(enricher.isRunning()); enricher.stop(); assertFalse(enricher.isRunning()); enricher.start(); 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 { private final String firstName; private final String lastName; Source(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public String getFirstName() { return firstName; } public String getLastName() { return lastName; } } public static final class Target implements Cloneable { private volatile String name; private volatile Target child; public Target() { this.name = "default"; } private Target(String name) { this.name = name; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public Target getChild() { return this.child; } public void setChild(Target child) { this.child = child; } @Override public Object clone() { Target clone = new Target(this.name); clone.setChild(this.child); return clone; } } public static final class TargetUser { private volatile String name; public TargetUser() { this.name = "default"; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } } public static final class UncloneableTargetUser implements Cloneable { private volatile String name; public UncloneableTargetUser() { this.name = "default"; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } @Override public Object clone() { throw new IllegalStateException("Cloning not possible"); } } }