/*
* 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.lang.reflect.Method;
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;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.integration.endpoint.IntegrationConsumer;
import org.springframework.integration.endpoint.MessageProducerSupport;
import org.springframework.integration.endpoint.PollingConsumer;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.gateway.GatewayProxyFactoryBean;
import org.springframework.integration.gateway.MessagingGatewaySupport;
import org.springframework.integration.handler.CompositeMessageHandler;
import org.springframework.integration.handler.DiscardingMessageHandler;
import org.springframework.integration.router.RecipientListRouter.Recipient;
import org.springframework.integration.router.RecipientListRouterManagement;
import org.springframework.integration.support.context.NamedComponent;
import org.springframework.integration.support.management.MappingMessageRouterManagement;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.util.StringUtils;
/**
* Builds the runtime object model graph.
*
* @author Gary Russell
* @author Artem Bilan
* @since 4.3
*
*/
public class IntegrationGraphServer implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent> {
private static final float GRAPH_VERSION = 1.0f;
private final NodeFactory nodeFactory = new NodeFactory();
private ApplicationContext applicationContext;
private Graph graph;
private String applicationName;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext; //NOSONAR (sync)
}
/**
* Set the application name that will appear in the 'contentDescriptor' under
* the 'name' key. If not provided, the property 'spring.application.name' from
* the application context environment will be used (if present).
* @param applicationName the application name.
*/
public void setApplicationName(String applicationName) {
this.applicationName = applicationName; //NOSONAR (sync)
}
/**
* Return the cached graph. Although the graph is cached, the data therein (stats
* etc.) are dynamic.
* @return the graph.
* @see #rebuild()
*/
public Graph getGraph() {
if (this.graph == null) { //NOSONAR (sync)
synchronized (this) {
if (this.graph == null) {
buildGraph();
}
}
}
return this.graph;
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext().equals(this.applicationContext)) {
buildGraph();
}
}
private synchronized Graph buildGraph() {
String implementationVersion = IntegrationGraphServer.class.getPackage().getImplementationVersion();
if (implementationVersion == null) {
implementationVersion = "unknown - is Spring Integration running from the distribution jar?";
}
Map<String, Object> descriptor = new HashMap<String, Object>();
descriptor.put("provider", "spring-integration");
descriptor.put("providerVersion", implementationVersion);
descriptor.put("providerFormatVersion", GRAPH_VERSION);
String name = this.applicationName;
if (name == null) {
name = this.applicationContext.getEnvironment().getProperty("spring.application.name");
}
if (name != null) {
descriptor.put("name", name);
}
this.nodeFactory.reset();
Collection<IntegrationNode> nodes = new ArrayList<IntegrationNode>();
Collection<LinkNode> links = new ArrayList<LinkNode>();
Map<String, MessageChannelNode> channelNodes = channels(nodes);
pollingAdapters(nodes, links, channelNodes);
gateways(nodes, links, channelNodes);
producers(nodes, links, channelNodes);
consumers(nodes, links, channelNodes);
this.graph = new Graph(descriptor, nodes, links);
return this.graph;
}
private Map<String, MessageChannelNode> channels(Collection<IntegrationNode> nodes) {
Map<String, MessageChannel> channels = this.applicationContext
.getBeansOfType(MessageChannel.class);
Map<String, MessageChannelNode> channelNodes = new HashMap<String, MessageChannelNode>();
for (Entry<String, MessageChannel> entry : channels.entrySet()) {
MessageChannel channel = entry.getValue();
MessageChannelNode channelNode = this.nodeFactory.channelNode(entry.getKey(), channel);
String beanName = entry.getKey();
nodes.add(channelNode);
channelNodes.put(beanName, channelNode);
}
return channelNodes;
}
private void pollingAdapters(Collection<IntegrationNode> nodes, Collection<LinkNode> links,
Map<String, MessageChannelNode> channelNodes) {
Map<String, SourcePollingChannelAdapter> spcas = this.applicationContext
.getBeansOfType(SourcePollingChannelAdapter.class);
for (Entry<String, SourcePollingChannelAdapter> entry : spcas.entrySet()) {
SourcePollingChannelAdapter adapter = entry.getValue();
MessageSourceNode sourceNode = this.nodeFactory.sourceNode(entry.getKey(), adapter);
nodes.add(sourceNode);
producerLink(links, channelNodes, sourceNode);
}
}
private void gateways(Collection<IntegrationNode> nodes, Collection<LinkNode> links,
Map<String, MessageChannelNode> channelNodes) {
Map<String, MessagingGatewaySupport> gateways = this.applicationContext
.getBeansOfType(MessagingGatewaySupport.class);
for (Entry<String, MessagingGatewaySupport> entry : gateways.entrySet()) {
MessagingGatewaySupport gateway = entry.getValue();
MessageGatewayNode gatewayNode = this.nodeFactory.gatewayNode(entry.getKey(), gateway);
nodes.add(gatewayNode);
producerLink(links, channelNodes, gatewayNode);
}
Map<String, GatewayProxyFactoryBean> gpfbs = this.applicationContext
.getBeansOfType(GatewayProxyFactoryBean.class);
for (Entry<String, GatewayProxyFactoryBean> entry : gpfbs.entrySet()) {
Map<Method, MessagingGatewaySupport> methodMap = entry.getValue().getGateways();
for (Entry<Method, MessagingGatewaySupport> gwEntry : methodMap.entrySet()) {
MessagingGatewaySupport gateway = gwEntry.getValue();
Method method = gwEntry.getKey();
Class<?>[] parameterTypes = method.getParameterTypes();
String[] parameterTypeNames = new String[parameterTypes.length];
int i = 0;
for (Class<?> type : parameterTypes) {
parameterTypeNames[i++] = type.getName();
}
String signature = method.getName() +
"(" + StringUtils.arrayToCommaDelimitedString(parameterTypeNames) + ")";
MessageGatewayNode gatewayNode = this.nodeFactory.gatewayNode(
entry.getKey().substring(1) + "." + signature, gateway);
nodes.add(gatewayNode);
producerLink(links, channelNodes, gatewayNode);
}
}
}
private void producers(Collection<IntegrationNode> nodes, Collection<LinkNode> links,
Map<String, MessageChannelNode> channelNodes) {
Map<String, MessageProducerSupport> producers = this.applicationContext
.getBeansOfType(MessageProducerSupport.class);
for (Entry<String, MessageProducerSupport> entry : producers.entrySet()) {
MessageProducerSupport producer = entry.getValue();
MessageProducerNode producerNode = this.nodeFactory.producerNode(entry.getKey(), producer);
nodes.add(producerNode);
producerLink(links, channelNodes, producerNode);
}
}
private void consumers(Collection<IntegrationNode> nodes, Collection<LinkNode> links,
Map<String, MessageChannelNode> channelNodes) {
Map<String, IntegrationConsumer> consumers = this.applicationContext.getBeansOfType(IntegrationConsumer.class);
for (Entry<String, IntegrationConsumer> entry : consumers.entrySet()) {
IntegrationConsumer consumer = entry.getValue();
MessageHandlerNode handlerNode = consumer instanceof PollingConsumer
? this.nodeFactory.polledHandlerNode(entry.getKey(), (PollingConsumer) consumer)
: this.nodeFactory.handlerNode(entry.getKey(), consumer);
nodes.add(handlerNode);
MessageChannelNode channelNode = channelNodes.get(handlerNode.getInput());
if (channelNode != null) {
links.add(new LinkNode(channelNode.getNodeId(), handlerNode.getNodeId(), LinkNode.Type.input));
}
producerLink(links, channelNodes, handlerNode);
}
}
private void producerLink(Collection<LinkNode> links, Map<String, MessageChannelNode> channelNodes,
EndpointNode endpointNode) {
MessageChannelNode channelNode;
if (endpointNode.getOutput() != null) {
channelNode = channelNodes.get(endpointNode.getOutput());
if (channelNode != null) {
links.add(new LinkNode(endpointNode.getNodeId(), channelNode.getNodeId(), LinkNode.Type.output));
}
}
if (endpointNode instanceof ErrorCapableNode) {
channelNode = channelNodes.get(((ErrorCapableNode) endpointNode).getErrors());
if (channelNode != null) {
links.add(new LinkNode(endpointNode.getNodeId(), channelNode.getNodeId(), LinkNode.Type.error));
}
}
if (endpointNode instanceof DiscardingMessageHandlerNode) {
channelNode = channelNodes.get(((DiscardingMessageHandlerNode) endpointNode).getDiscards());
if (channelNode != null) {
links.add(new LinkNode(endpointNode.getNodeId(), channelNode.getNodeId(), LinkNode.Type.discard));
}
}
if (endpointNode instanceof RoutingMessageHandlerNode) {
Collection<String> routes = ((RoutingMessageHandlerNode) endpointNode).getRoutes();
for (String route : routes) {
channelNode = channelNodes.get(route);
if (channelNode != null) {
links.add(new LinkNode(endpointNode.getNodeId(), channelNode.getNodeId(), LinkNode.Type.route));
}
}
}
}
/**
* Rebuild the graph, re-cache it, and return it. Use this method if the application
* components have changed (added or removed).
* @return the graph.
* @see #getGraph()
*/
public Graph rebuild() {
return buildGraph();
}
private static final class NodeFactory {
private final AtomicInteger nodeId = new AtomicInteger();
NodeFactory() {
super();
}
private MessageChannelNode channelNode(String name, MessageChannel channel) {
return new MessageChannelNode(this.nodeId.incrementAndGet(), name, channel);
}
private MessageGatewayNode gatewayNode(String name, MessagingGatewaySupport gateway) {
String errorChannel = gateway.getErrorChannel() != null ? gateway.getErrorChannel().toString() : null;
return new MessageGatewayNode(this.nodeId.incrementAndGet(), name, gateway,
gateway.getRequestChannel().toString(), errorChannel);
}
private MessageProducerNode producerNode(String name, MessageProducerSupport producer) {
String errorChannel = producer.getErrorChannel() != null ? producer.getErrorChannel().toString() : null;
return new MessageProducerNode(this.nodeId.incrementAndGet(), name, producer,
producer.getOutputChannel().toString(), errorChannel);
}
private MessageSourceNode sourceNode(String name, SourcePollingChannelAdapter adapter) {
String errorChannel = adapter.getDefaultErrorChannel() != null
? adapter.getDefaultErrorChannel().toString() : null;
return new MessageSourceNode(this.nodeId.incrementAndGet(), name, adapter.getMessageSource(),
adapter.getOutputChannel().toString(), errorChannel);
}
private MessageHandlerNode handlerNode(String name, IntegrationConsumer consumer) {
MessageChannel outputChannel = consumer.getOutputChannel();
String outputChannelName = outputChannel == null ? null : outputChannel.toString();
MessageHandler handler = consumer.getHandler();
if (handler instanceof CompositeMessageHandler) {
return compositeHandler(name, consumer, (CompositeMessageHandler) handler, outputChannelName, null,
false);
}
else if (handler instanceof DiscardingMessageHandler) {
return discardingHandler(name, consumer, (DiscardingMessageHandler) handler, outputChannelName, null,
false);
}
else if (handler instanceof MappingMessageRouterManagement) {
return routingHandler(name, consumer, handler, (MappingMessageRouterManagement) handler,
outputChannelName, null, false);
}
else if (handler instanceof RecipientListRouterManagement) {
return recipientListRoutingHandler(name, consumer, handler, (RecipientListRouterManagement) handler,
outputChannelName, null, false);
}
else {
return new MessageHandlerNode(this.nodeId.incrementAndGet(), name, handler,
consumer.getInputChannel().toString(), outputChannelName);
}
}
private MessageHandlerNode polledHandlerNode(String name, PollingConsumer consumer) {
MessageChannel outputChannel = consumer.getOutputChannel();
String outputChannelName = outputChannel == null ? null : outputChannel.toString();
String errorChannel = consumer.getDefaultErrorChannel() != null
? consumer.getDefaultErrorChannel().toString() : null;
MessageHandler handler = consumer.getHandler();
if (handler instanceof CompositeMessageHandler) {
return compositeHandler(name, consumer, (CompositeMessageHandler) handler, outputChannelName,
errorChannel, true);
}
else if (handler instanceof DiscardingMessageHandler) {
return discardingHandler(name, consumer, (DiscardingMessageHandler) handler, outputChannelName,
errorChannel, true);
}
else if (handler instanceof MappingMessageRouterManagement) {
return routingHandler(name, consumer, handler, (MappingMessageRouterManagement) handler,
outputChannelName, errorChannel, true);
}
else if (handler instanceof RecipientListRouterManagement) {
return recipientListRoutingHandler(name, consumer, handler, (RecipientListRouterManagement) handler,
outputChannelName, errorChannel, true);
}
else {
return 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) {
String discards = handler.getDiscardChannel() != null ? handler.getDiscardChannel().toString() : null;
return polled
? new ErrorCapableDiscardingMessageHandlerNode(this.nodeId.incrementAndGet(), name, handler,
consumer.getInputChannel().toString(), output, discards, errors)
: new DiscardingMessageHandlerNode(this.nodeId.incrementAndGet(), name, handler,
consumer.getInputChannel().toString(), output, discards);
}
private MessageHandlerNode routingHandler(String name, IntegrationConsumer consumer, MessageHandler handler,
MappingMessageRouterManagement router, String output, String errors, boolean polled) {
Collection<String> routes = router.getChannelMappings().values();
Collection<String> dynamicChannelNames = router.getDynamicChannelNames();
if (dynamicChannelNames.size() > 0) {
routes = new ArrayList<String>(routes);
routes.addAll(dynamicChannelNames);
}
return polled
? new ErrorCapableRoutingNode(this.nodeId.incrementAndGet(), name, handler,
consumer.getInputChannel().toString(), output, errors, routes)
: new RoutingMessageHandlerNode(this.nodeId.incrementAndGet(), name, handler,
consumer.getInputChannel().toString(), output, routes);
}
private MessageHandlerNode recipientListRoutingHandler(String name, IntegrationConsumer consumer,
MessageHandler handler, RecipientListRouterManagement router, String output, String errors,
boolean polled) {
Collection<?> recipients = router.getRecipients();
List<String> routes = new ArrayList<String>(recipients.size());
for (Object recipient : recipients) {
routes.add(((Recipient) recipient).getChannel().toString());
}
return polled
? new ErrorCapableRoutingNode(this.nodeId.incrementAndGet(), name, handler,
consumer.getInputChannel().toString(), output, errors, routes)
: new RoutingMessageHandlerNode(this.nodeId.incrementAndGet(), name, handler,
consumer.getInputChannel().toString(), output, routes);
}
private void reset() {
this.nodeId.set(0);
}
}
}