/*
* 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.channel.interceptor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.context.Lifecycle;
import org.springframework.integration.channel.ChannelInterceptorAware;
import org.springframework.integration.core.MessageSelector;
import org.springframework.integration.support.channel.BeanFactoryChannelResolver;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.util.Assert;
/**
* A {@link ChannelInterceptor} that publishes a copy of the intercepted message
* to a secondary target while still sending the original message to the main channel.
*
* @author Mark Fisher
* @author Gary Russell
* @author Artem Bilan
*/
@ManagedResource
public class WireTap extends ChannelInterceptorAdapter
implements Lifecycle, VetoCapableInterceptor, BeanFactoryAware {
private static final Log logger = LogFactory.getLog(WireTap.class);
private volatile MessageChannel channel;
private volatile String channelName;
private volatile long timeout = 0;
private final MessageSelector selector;
private volatile boolean running = true;
private BeanFactory beanFactory;
/**
* Create a new wire tap with <em>no</em> {@link MessageSelector}.
* @param channel the MessageChannel to which intercepted messages will be sent
*/
public WireTap(MessageChannel channel) {
this(channel, null);
}
/**
* Create a new wire tap with the provided {@link MessageSelector}.
* @param channel the channel to which intercepted messages will be sent
* @param selector the selector that must accept a message for it to be
* sent to the intercepting channel
*/
public WireTap(MessageChannel channel, MessageSelector selector) {
Assert.notNull(channel, "channel must not be null");
this.channel = channel;
this.selector = selector;
}
/**
* Create a new wire tap based on the MessageChannel name and
* with <em>no</em> {@link MessageSelector}.
* @param channelName the name of the target MessageChannel
* to which intercepted messages will be sent
* @since 4.3
*/
public WireTap(String channelName) {
this(channelName, null);
}
/**
* Create a new wire tap with the provided {@link MessageSelector}.
* @param channelName the name of the target MessageChannel
* to which intercepted messages will be sent.
* @param selector the selector that must accept a message for it to be
* sent to the intercepting channel
* @since 4.3
*/
public WireTap(String channelName, MessageSelector selector) {
Assert.hasText(channelName, "channelName must not be empty");
this.channelName = channelName;
this.selector = selector;
}
/**
* Specify the timeout value for sending to the intercepting target.
* @param timeout the timeout in milliseconds
*/
public void setTimeout(long timeout) {
this.timeout = timeout;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (this.beanFactory == null) {
this.beanFactory = beanFactory;
}
}
/**
* Check whether the wire tap is currently running.
*/
@Override
@ManagedAttribute
public boolean isRunning() {
return this.running;
}
/**
* Restart the wire tap if it has been stopped. It is running by default.
*/
@Override
@ManagedOperation
public void start() {
this.running = true;
}
/**
* Stop the wire tap. To restart, invoke {@link #start()}.
*/
@Override
@ManagedOperation
public void stop() {
this.running = false;
}
/**
* Intercept the Message and, <em>if accepted</em> by the {@link MessageSelector},
* send it to the secondary target. If this wire tap's {@link MessageSelector} is
* <code>null</code>, it will accept all messages.
*/
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
MessageChannel wireTapChannel = getChannel();
if (wireTapChannel.equals(channel)) {
if (logger.isDebugEnabled()) {
logger.debug("WireTap is refusing to intercept its own channel '" + wireTapChannel + "'");
}
return message;
}
if (this.running && (this.selector == null || this.selector.accept(message))) {
boolean sent = (this.timeout >= 0)
? wireTapChannel.send(message, this.timeout)
: wireTapChannel.send(message);
if (!sent && logger.isWarnEnabled()) {
logger.warn("failed to send message to WireTap channel '" + wireTapChannel + "'");
}
}
return message;
}
@Override
public boolean shouldIntercept(String beanName, ChannelInterceptorAware channel) {
return !getChannel().equals(channel);
}
private MessageChannel getChannel() {
if (this.channelName != null) {
synchronized (this) {
if (this.channelName != null) {
this.channel = new BeanFactoryChannelResolver(this.beanFactory)
.resolveDestination(this.channelName);
this.channelName = null;
}
}
}
return this.channel;
}
}