INT-4009: Support Composite Handler in Graph

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

Adds:

    ...
    "handlers" : [ {
      "name" : "polledChain$child#0",
      "type" : "transformer"
    }, {
      "name" : "polledChain$child#1",
      "type" : "service-activator"
    } ],
    ...

to a chain node.

INT-4010: Add Discard Flows to Object Model

JIRA: https://jira.spring.io/browse/INT-4010
This commit is contained in:
Gary Russell
2016-04-25 14:34:30 -04:00
committed by Artem Bilan
parent 590398f972
commit 6a9a42562c
12 changed files with 402 additions and 32 deletions

View File

@@ -43,6 +43,7 @@ import org.springframework.integration.IntegrationMessageHeaderAccessor;
import org.springframework.integration.channel.NullChannel;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.handler.AbstractMessageProducingHandler;
import org.springframework.integration.handler.DiscardingMessageHandler;
import org.springframework.integration.store.MessageGroup;
import org.springframework.integration.store.MessageGroupStore;
import org.springframework.integration.store.MessageGroupStore.MessageGroupCallback;
@@ -85,7 +86,7 @@ import org.springframework.util.CollectionUtils;
* @since 2.0
*/
public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageProducingHandler
implements DisposableBean, ApplicationEventPublisherAware {
implements DiscardingMessageHandler, DisposableBean, ApplicationEventPublisherAware {
private static final Log logger = LogFactory.getLog(AbstractCorrelatingMessageHandler.class);
@@ -329,7 +330,16 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageP
return this.releaseStrategy;
}
protected MessageChannel getDiscardChannel() {
@Override
public MessageChannel getDiscardChannel() {
if (this.discardChannelName != null) {
synchronized (this) {
if (this.discardChannelName != null) {
this.discardChannel = getChannelResolver().resolveDestination(this.discardChannelName);
this.discardChannelName = null;
}
}
}
return this.discardChannel;
}
@@ -476,15 +486,8 @@ public abstract class AbstractCorrelatingMessageHandler extends AbstractMessageP
}
private void discardMessage(Message<?> message) {
if (this.discardChannelName != null) {
synchronized (this) {
if (this.discardChannelName != null) {
this.discardChannel = getChannelResolver().resolveDestination(this.discardChannelName);
this.discardChannelName = null;
}
}
}
this.messagingTemplate.send(this.discardChannel, message);
MessageChannel discardChannel = getDiscardChannel();
this.messagingTemplate.send(discardChannel, message);
}
/**

View File

@@ -21,6 +21,7 @@ import org.springframework.context.Lifecycle;
import org.springframework.integration.MessageRejectedException;
import org.springframework.integration.core.MessageSelector;
import org.springframework.integration.handler.AbstractReplyProducingPostProcessingMessageHandler;
import org.springframework.integration.handler.DiscardingMessageHandler;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.util.Assert;
@@ -40,7 +41,8 @@ import org.springframework.util.Assert;
* @author Artem Bilan
* @author David Liu
*/
public class MessageFilter extends AbstractReplyProducingPostProcessingMessageHandler implements Lifecycle {
public class MessageFilter extends AbstractReplyProducingPostProcessingMessageHandler
implements DiscardingMessageHandler, Lifecycle {
private final MessageSelector selector;
@@ -104,6 +106,20 @@ public class MessageFilter extends AbstractReplyProducingPostProcessingMessageHa
this.setPostProcessWithinAdvice(discardWithinAdvice);
}
@Override
public MessageChannel getDiscardChannel() {
if (this.discardChannelName != null) {
synchronized (this) {
if (this.discardChannelName != null) {
this.discardChannel = getChannelResolver().resolveDestination(this.discardChannelName);
this.discardChannelName = null;
}
}
}
return this.discardChannel;
}
@Override
public String getComponentType() {
return "filter";
@@ -153,16 +169,9 @@ public class MessageFilter extends AbstractReplyProducingPostProcessingMessageHa
@Override
public Object postProcess(Message<?> message, Object result) {
if (result == null) {
if (this.discardChannelName != null) {
synchronized (this) {
if (this.discardChannelName != null) {
this.discardChannel = getChannelResolver().resolveDestination(this.discardChannelName);
this.discardChannelName = null;
}
}
}
if (this.discardChannel != null) {
this.messagingTemplate.send(this.discardChannel, message);
MessageChannel discardChannel = getDiscardChannel();
if (discardChannel != null) {
this.messagingTemplate.send(discardChannel, message);
}
if (this.throwExceptionOnRejection) {
throw new MessageRejectedException(message, "MessageFilter '" + this.getComponentName()

View File

@@ -0,0 +1,38 @@
/*
* Copyright 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.handler;
import java.util.List;
import org.springframework.messaging.MessageHandler;
/**
* Classes implementing this interface delegate to a list of handlers.
*
* @author Gary Russell
* @since 4.3
*
*/
public interface CompositeMessageHandler extends MessageHandler {
/**
* Return an unmodifiable list of handlers.
* @return the handlers.
*/
List<MessageHandler> getHandlers();
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 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.handler;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
/**
* Classes implementing this interface are capable of discarding messages.
*
* @author Gary Russell
* @since 4.3
*
*/
public interface DiscardingMessageHandler extends MessageHandler {
/**
* Return the discard channel.
* @return the channel.
*/
MessageChannel getDiscardChannel();
}

View File

@@ -16,6 +16,7 @@
package org.springframework.integration.handler;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
@@ -62,7 +63,8 @@ import org.springframework.util.Assert;
* @author Gary Russell
* @author Artem Bilan
*/
public class MessageHandlerChain extends AbstractMessageProducingHandler implements MessageProducer, Lifecycle {
public class MessageHandlerChain extends AbstractMessageProducingHandler implements MessageProducer,
CompositeMessageHandler, Lifecycle {
private volatile List<MessageHandler> handlers;
@@ -78,6 +80,11 @@ public class MessageHandlerChain extends AbstractMessageProducingHandler impleme
this.handlers = handlers;
}
@Override
public List<MessageHandler> getHandlers() {
return Collections.unmodifiableList(this.handlers);
}
@Override
public String getComponentType() {
return "chain";

View File

@@ -0,0 +1,66 @@
/*
* Copyright 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.support.management.graph;
import java.util.ArrayList;
import java.util.List;
import org.springframework.messaging.MessageHandler;
/**
* Represents a composite message handler.
*
* @author Gary Russell
* @since 4.3
*
*/
public class CompositeMessageHandlerNode extends MessageHandlerNode {
private final List<InnerHandler> handlers = new ArrayList<InnerHandler>();
public CompositeMessageHandlerNode(int nodeId, String name, MessageHandler handler, String input, String output,
List<InnerHandler> handlers) {
super(nodeId, name, handler, input, output);
this.handlers.addAll(handlers);
}
public List<InnerHandler> getHandlers() {
return this.handlers;
}
public static class InnerHandler {
private final String name;
private final String type;
public InnerHandler(String name, String type) {
this.name = name;
this.type = type;
}
public String getName() {
return this.name;
}
public String getType() {
return this.type;
}
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 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.support.management.graph;
import org.springframework.messaging.MessageHandler;
/**
* Represents an endpoint that has a discard channel.
*
* @author Gary Russell
* @since 4.3
*
*/
public class DiscardingMessageHandlerNode extends MessageHandlerNode {
private final String discards;
public DiscardingMessageHandlerNode(int nodeId, String name, MessageHandler handler, String input, String output,
String discards) {
super(nodeId, name, handler, input, output);
this.discards = discards;
}
public String getDiscards() {
return this.discards;
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 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.support.management.graph;
import java.util.List;
import org.springframework.integration.handler.CompositeMessageHandler;
/**
* Represents a composite message handler that can emit error messages
* (pollable endpoint).
*
* @author Gary Russell
* @since 4.3
*
*/
public class ErrorCapableCompositeMessageHandlerNode extends CompositeMessageHandlerNode implements ErrorCapableNode {
private final String errors;
public ErrorCapableCompositeMessageHandlerNode(int nodeId, String name, CompositeMessageHandler handler, String input,
String output, String errors, List<InnerHandler> handlers) {
super(nodeId, name, handler, input, output, handlers);
this.errors = errors;
}
@Override
public String getErrors() {
return this.errors;
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 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.support.management.graph;
import org.springframework.messaging.MessageHandler;
/**
* Represents an endpoint that has a discard channel and can emit errors
* (pollable endpoint).
*
* @author Gary Russell
* @since 4.3
*
*/
public class ErrorCapableDiscardingMessageHandlerNode extends DiscardingMessageHandlerNode implements ErrorCapableNode {
private final String errors;
public ErrorCapableDiscardingMessageHandlerNode(int nodeId, String name, MessageHandler handler, String input,
String output, String discards, String errors) {
super(nodeId, name, handler, input, output, discards);
this.errors = errors;
}
@Override
public String getErrors() {
return this.errors;
}
}

View File

@@ -19,6 +19,7 @@ package org.springframework.integration.support.management.graph;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;
@@ -33,7 +34,11 @@ import org.springframework.integration.endpoint.MessageProducerSupport;
import org.springframework.integration.endpoint.PollingConsumer;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.gateway.MessagingGatewaySupport;
import org.springframework.integration.handler.CompositeMessageHandler;
import org.springframework.integration.handler.DiscardingMessageHandler;
import org.springframework.integration.support.context.NamedComponent;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
/**
* Builds the runtime object model graph.
@@ -203,6 +208,12 @@ public class IntegrationGraphServer implements ApplicationContextAware, Applicat
links.add(new LinkNode(endpointNode.getNodeId(), channelNode.getNodeId()));
}
}
if (endpointNode instanceof DiscardingMessageHandlerNode) {
channelNode = channelNodes.get(((DiscardingMessageHandlerNode) endpointNode).getDiscards());
if (channelNode != null) {
links.add(new LinkNode(endpointNode.getNodeId(), channelNode.getNodeId()));
}
}
}
/**
@@ -245,8 +256,14 @@ public class IntegrationGraphServer implements ApplicationContextAware, Applicat
private MessageHandlerNode handlerNode(String name, IntegrationConsumer consumer) {
MessageChannel outputChannel = consumer.getOutputChannel();
String outputChannelName = outputChannel == null ? null : outputChannel.toString();
return new MessageHandlerNode(this.nodeId.incrementAndGet(), name, consumer.getHandler(),
consumer.getInputChannel().toString(), outputChannelName);
MessageHandler handler = consumer.getHandler();
return handler instanceof CompositeMessageHandler
? compositeHandler(name, consumer, (CompositeMessageHandler) handler, outputChannelName, null, false)
: handler instanceof DiscardingMessageHandler
? discardingHandler(name, consumer, (DiscardingMessageHandler) handler, outputChannelName, null,
false)
: new MessageHandlerNode(this.nodeId.incrementAndGet(), name, handler,
consumer.getInputChannel().toString(), outputChannelName);
}
private MessageHandlerNode polledHandlerNode(String name, PollingConsumer consumer) {
@@ -254,8 +271,43 @@ public class IntegrationGraphServer implements ApplicationContextAware, Applicat
String outputChannelName = outputChannel == null ? null : outputChannel.toString();
String errorChannel = consumer.getDefaultErrorChannel() != null
? consumer.getDefaultErrorChannel().toString() : null;
return new ErrorCapableMessageHandlerNode(this.nodeId.incrementAndGet(), name, consumer.getHandler(),
consumer.getInputChannel().toString(), outputChannelName, errorChannel);
MessageHandler handler = consumer.getHandler();
return handler instanceof CompositeMessageHandler
? compositeHandler(name, consumer, (CompositeMessageHandler) handler, outputChannelName, errorChannel,
true)
: handler instanceof DiscardingMessageHandler
? discardingHandler(name, consumer, (DiscardingMessageHandler) handler, outputChannelName,
errorChannel, true)
: new ErrorCapableMessageHandlerNode(this.nodeId.incrementAndGet(), name, handler,
consumer.getInputChannel().toString(), outputChannelName, errorChannel);
}
private MessageHandlerNode compositeHandler(String name, IntegrationConsumer consumer,
CompositeMessageHandler handler, String output, String errors, boolean polled) {
List<MessageHandler> handlers = handler.getHandlers();
List<CompositeMessageHandlerNode.InnerHandler> innerHandlers =
new ArrayList<CompositeMessageHandlerNode.InnerHandler>();
for (MessageHandler innerHandler : handlers) {
if (innerHandler instanceof NamedComponent) {
NamedComponent named = (NamedComponent) innerHandler;
innerHandlers.add(new CompositeMessageHandlerNode.InnerHandler(named.getComponentName(),
named.getComponentType()));
}
}
return polled
? new ErrorCapableCompositeMessageHandlerNode(this.nodeId.incrementAndGet(), name, handler,
consumer.getInputChannel().toString(), output, errors, innerHandlers)
: new CompositeMessageHandlerNode(this.nodeId.incrementAndGet(), name, handler,
consumer.getInputChannel().toString(), output, innerHandlers);
}
private MessageHandlerNode discardingHandler(String name, IntegrationConsumer consumer,
DiscardingMessageHandler handler, String output, String errors, boolean polled) {
return polled
? new ErrorCapableDiscardingMessageHandlerNode(this.nodeId.incrementAndGet(), name, handler,
consumer.getInputChannel().toString(), output, handler.getDiscardChannel().toString(), errors)
: new DiscardingMessageHandlerNode(this.nodeId.incrementAndGet(), name, handler,
consumer.getInputChannel().toString(), output, handler.getDiscardChannel().toString());
}
private void reset() {

View File

@@ -31,6 +31,7 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.MessagePublishingErrorHandler;
@@ -48,6 +49,7 @@ import org.springframework.messaging.MessagingException;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.scheduling.support.PeriodicTrigger;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -59,6 +61,7 @@ import com.fasterxml.jackson.databind.SerializationFeature;
*
*/
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class IntegrationGraphServerTests {
@Autowired
@@ -77,16 +80,17 @@ public class IntegrationGraphServerTests {
@SuppressWarnings("unchecked")
List<Map<?, ?>> nodes = (List<Map<?, ?>>) map.get("nodes");
assertThat(nodes, is(notNullValue()));
assertThat(nodes.size(), is(equalTo(13)));
assertThat(nodes.size(), is(equalTo(19)));
@SuppressWarnings("unchecked")
List<Map<?, ?>> links = (List<Map<?, ?>>) map.get("links");
assertThat(links, is(notNullValue()));
assertThat(links.size(), is(equalTo(9)));
assertThat(links.size(), is(equalTo(17)));
}
@Configuration
@EnableIntegration
@EnableIntegrationManagement
@ImportResource("org/springframework/integration/support/management/graph/integration-graph-context.xml")
public static class Config {
@Bean
@@ -129,7 +133,7 @@ public class IntegrationGraphServerTests {
}
@Bean
public PollableChannel two() {
public PollableChannel polledChannel() {
return new QueueChannel();
}
@@ -162,12 +166,12 @@ public class IntegrationGraphServerTests {
public static class Services {
@ServiceActivator(inputChannel = "one", outputChannel = "two")
@ServiceActivator(inputChannel = "one", outputChannel = "polledChannel")
public String foo(String foo) {
return foo.toUpperCase();
}
@ServiceActivator(inputChannel = "two")
@ServiceActivator(inputChannel = "polledChannel")
public void bar(String foo) {
}

View File

@@ -0,0 +1,22 @@
<?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">
<int:chain id="myChain" input-channel="chainChannel">
<int:transformer expression="payload" />
<int:service-activator expression="payload" />
</int:chain>
<int:chain id="polledChain" input-channel="polledChannel">
<int:transformer expression="payload" />
<int:service-activator expression="payload" />
</int:chain>
<int:filter id="myFilter" input-channel="filterChannel" discard-channel="three" expression="true" />
<int:aggregator id="myAggregator" input-channel="polledChannel" discard-channel="three" />
</beans>