/* * 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.router; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.stream.Collectors; import org.springframework.beans.factory.InitializingBean; import org.springframework.integration.core.MessageSelector; import org.springframework.integration.filter.ExpressionEvaluatingSelector; import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.core.DestinationResolver; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * <pre class="code"> * {@code * <recipient-list-router id="simpleRouter" input-channel="routingChannelA"> * <recipient channel="channel1"/> * <recipient channel="channel2"/> * </recipient-list-router> * } * </pre> * <p> * A Message Router that sends Messages to a list of recipient channels. The * recipients can be provided as a static list of {@link MessageChannel} * instances via the {@link #setChannels(List)} method, or for dynamic behavior, * the values can be provided via the {@link #setRecipients(List)} method. * <p> * For more advanced, programmatic control of dynamic recipient lists, consider * using the @Router annotation or extending {@link AbstractMappingMessageRouter} instead. * <p> * Contrary to a standard <router .../> this handler will try to send to * all channels that are configured as recipients. It is to channels what a * publish subscribe channel is to endpoints. * <p> * Using this class only makes sense if it is essential to send messages on * multiple channels instead of sending them to multiple handlers. If the latter * is an option using a publish subscribe channel is the more flexible solution. * * @author Mark Fisher * @author Oleg Zhurakousky * @author Artem Bilan * @author Liujiong */ public class RecipientListRouter extends AbstractMessageRouter implements InitializingBean, RecipientListRouterManagement { private volatile Queue<Recipient> recipients = new ConcurrentLinkedQueue<>(); /** * Set the channels for this router. Either call this method or * {@link #setRecipients(List)} but not both. If MessageSelectors should be * considered, then use {@link #setRecipients(List)}. * @param channels The channels. */ public void setChannels(List<MessageChannel> channels) { Assert.notEmpty(channels, "'channels' must not be empty"); List<Recipient> recipients = channels.stream() .map(Recipient::new) .collect(Collectors.toList()); setRecipients(recipients); } /** * Set the recipients for this router. * @param recipients The recipients. */ public void setRecipients(List<Recipient> recipients) { Assert.notEmpty(recipients, "'recipients' must not be empty"); Queue<Recipient> newRecipients = new ConcurrentLinkedQueue<>(recipients); if (getBeanFactory() != null) { newRecipients.forEach(recipient -> recipient.setChannelResolver(getChannelResolver())); } if (logger.isDebugEnabled()) { logger.debug("Channel Recipients: " + this.recipients + " replaced with: " + newRecipients); } this.recipients = newRecipients; } /** * Set the recipients for this router. * @param recipientMappings map contains channelName and expression */ @Override @ManagedAttribute public void setRecipientMappings(Map<String, String> recipientMappings) { Assert.notEmpty(recipientMappings, "'recipientMappings' must not be empty"); Assert.noNullElements(recipientMappings.keySet().toArray(), "'recipientMappings' cannot have null keys."); Queue<Recipient> newRecipients = new ConcurrentLinkedQueue<>(); for (Entry<String, String> next : recipientMappings.entrySet()) { if (StringUtils.hasText(next.getValue())) { addRecipient(next.getKey(), next.getValue(), newRecipients); } else { addRecipient(next.getKey(), (MessageSelector) null, newRecipients); } } if (logger.isDebugEnabled()) { logger.debug("Channel Recipients: " + this.recipients + " replaced with: " + newRecipients); } this.recipients = newRecipients; } @Override @ManagedOperation public void addRecipient(String channelName, String selectorExpression) { addRecipient(channelName, selectorExpression, this.recipients); } private void addRecipient(String channelName, String selectorExpression, Queue<Recipient> recipients) { Assert.hasText(channelName, "'channelName' must not be empty."); Assert.hasText(selectorExpression, "'selectorExpression' must not be empty."); ExpressionEvaluatingSelector expressionEvaluatingSelector = new ExpressionEvaluatingSelector(selectorExpression); expressionEvaluatingSelector.setBeanFactory(getBeanFactory()); Recipient recipient = new Recipient(channelName, expressionEvaluatingSelector); if (getBeanFactory() != null) { recipient.setChannelResolver(getChannelResolver()); } recipients.add(recipient); } @Override @ManagedOperation public void addRecipient(String channelName) { addRecipient(channelName, (MessageSelector) null); } public void addRecipient(String channelName, MessageSelector selector) { addRecipient(channelName, selector, this.recipients); } private void addRecipient(String channelName, MessageSelector selector, Queue<Recipient> recipients) { Assert.hasText(channelName, "'channelName' must not be empty."); Recipient recipient = new Recipient(channelName, selector); if (getBeanFactory() != null) { recipient.setChannelResolver(getChannelResolver()); } recipients.add(recipient); } public void addRecipient(MessageChannel channel) { addRecipient(channel, null); } public void addRecipient(MessageChannel channel, MessageSelector selector) { Recipient recipient = new Recipient(channel, selector); if (getBeanFactory() != null) { recipient.setChannelResolver(getChannelResolver()); } this.recipients.add(recipient); } @Override @ManagedOperation public int removeRecipient(String channelName) { int counter = 0; MessageChannel channel = getChannelResolver().resolveDestination(channelName); for (Iterator<Recipient> it = this.recipients.iterator(); it.hasNext(); ) { if (it.next().getChannel() == channel) { it.remove(); counter++; } } return counter; } @Override @ManagedOperation public int removeRecipient(String channelName, String selectorExpression) { int counter = 0; MessageChannel targetChannel = getChannelResolver().resolveDestination(channelName); for (Iterator<Recipient> it = this.recipients.iterator(); it.hasNext(); ) { Recipient next = it.next(); MessageSelector selector = next.getSelector(); MessageChannel channel = next.getChannel(); if (selector instanceof ExpressionEvaluatingSelector && channel == targetChannel && ((ExpressionEvaluatingSelector) selector).getExpressionString().equals(selectorExpression)) { it.remove(); counter++; } } return counter; } @Override @ManagedOperation public void replaceRecipients(Properties recipientMappings) { Assert.notEmpty(recipientMappings, "'recipientMappings' must not be empty"); Set<String> keys = recipientMappings.stringPropertyNames(); Queue<Recipient> originalRecipients = this.recipients; this.recipients.clear(); for (String key : keys) { Assert.notNull(key, "channelName can't be null."); if (StringUtils.hasText(recipientMappings.getProperty(key))) { this.addRecipient(key, recipientMappings.getProperty(key)); } else { this.addRecipient(key); } } if (logger.isDebugEnabled()) { logger.debug("Channel Recipients:" + originalRecipients + " replaced with:" + this.recipients); } } @Override @ManagedAttribute public Collection<Recipient> getRecipients() { return Collections.unmodifiableCollection(this.recipients); } @Override public String getComponentType() { return "recipient-list-router"; } @Override protected Collection<MessageChannel> determineTargetChannels(Message<?> message) { return this.recipients.stream() .filter(recipient -> recipient.accept(message)) .map(Recipient::getChannel) .collect(Collectors.toList()); } @Override protected void onInit() throws Exception { super.onInit(); this.recipients.forEach(recipient -> recipient.setChannelResolver(getChannelResolver())); } public static class Recipient { private final MessageSelector selector; private MessageChannel channel; private String channelName; private DestinationResolver<MessageChannel> channelResolver; public Recipient(MessageChannel channel) { this(channel, null); } public Recipient(MessageChannel channel, MessageSelector selector) { this.channel = channel; this.selector = selector; } public Recipient(String channelName) { this(channelName, null); } public Recipient(String channelName, MessageSelector selector) { this.channelName = channelName; this.selector = selector; } public void setChannelResolver(DestinationResolver<MessageChannel> channelResolver) { this.channelResolver = channelResolver; } private MessageSelector getSelector() { return this.selector; } public MessageChannel getChannel() { String channelName = this.channelName; if (channelName != null) { if (this.channelResolver != null) { this.channel = this.channelResolver.resolveDestination(channelName); this.channelName = null; } } return this.channel; } public boolean accept(Message<?> message) { return (this.selector == null || this.selector.accept(message)); } } }