/*
* Copyright 2002-2017 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.endpoint;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.core.AttributeAccessor;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.history.MessageHistory;
import org.springframework.integration.support.DefaultErrorMessageStrategy;
import org.springframework.integration.support.ErrorMessageStrategy;
import org.springframework.integration.support.ErrorMessageUtils;
import org.springframework.integration.support.management.TrackableComponent;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* A support class for producer endpoints that provides a setter for the
* output channel and a convenience method for sending Messages.
*
* @author Mark Fisher
* @author Artem Bilan
* @author Gary Russell
*/
public abstract class MessageProducerSupport extends AbstractEndpoint implements MessageProducer, TrackableComponent,
SmartInitializingSingleton {
private final MessagingTemplate messagingTemplate = new MessagingTemplate();
private ErrorMessageStrategy errorMessageStrategy = new DefaultErrorMessageStrategy();
private volatile MessageChannel outputChannel;
private volatile String outputChannelName;
private volatile MessageChannel errorChannel;
private volatile String errorChannelName;
private volatile boolean shouldTrack = false;
protected MessageProducerSupport() {
this.setPhase(Integer.MAX_VALUE / 2);
}
@Override
public void setOutputChannel(MessageChannel outputChannel) {
this.outputChannel = outputChannel;
}
/**
* Set the output channel name; overrides
* {@link #setOutputChannel(MessageChannel) outputChannel} if provided.
* @param outputChannelName the channel name.
* @since 4.3
*/
public void setOutputChannelName(String outputChannelName) {
Assert.hasText(outputChannelName, "'outputChannelName' must not be null or empty");
this.outputChannelName = outputChannelName;
}
@Override
public MessageChannel getOutputChannel() {
if (this.outputChannelName != null) {
synchronized (this) {
if (this.outputChannelName != null) {
this.outputChannel = getChannelResolver().resolveDestination(this.outputChannelName);
this.outputChannelName = null;
}
}
}
return this.outputChannel;
}
public void setErrorChannel(MessageChannel errorChannel) {
this.errorChannel = errorChannel;
}
/**
* Set the error channel name. If no error channel is provided, this endpoint will
* propagate Exceptions to the message-driven source. To completely suppress
* Exceptions, provide a reference to the "nullChannel" here.
* @param errorChannelName The error channel bean name.
* @since 4.3
*/
public void setErrorChannelName(String errorChannelName) {
Assert.hasText(errorChannelName, "'errorChannelName' must not be empty");
this.errorChannelName = errorChannelName;
}
/**
* Return the error channel (if provided) to which error messages will
* be routed.
* @return the channel or null.
* @since 4.3
*/
public MessageChannel getErrorChannel() {
if (this.errorChannelName != null) {
synchronized (this) {
if (this.errorChannelName != null) {
this.errorChannel = getChannelResolver().resolveDestination(this.errorChannelName);
this.errorChannelName = null;
}
}
}
return this.errorChannel;
}
public void setSendTimeout(long sendTimeout) {
this.messagingTemplate.setSendTimeout(sendTimeout);
}
@Override
public void setShouldTrack(boolean shouldTrack) {
this.shouldTrack = shouldTrack;
}
/**
* Set an {@link ErrorMessageStrategy} to use to build an error message when a exception occurs.
* Default is the {@link DefaultErrorMessageStrategy}.
* @param errorMessageStrategy the {@link ErrorMessageStrategy}.
* @since 4.3.10
*/
public final void setErrorMessageStrategy(ErrorMessageStrategy errorMessageStrategy) {
Assert.notNull(errorMessageStrategy, "'errorMessageStrategy' cannot be null");
this.errorMessageStrategy = errorMessageStrategy;
}
protected MessagingTemplate getMessagingTemplate() {
return this.messagingTemplate;
}
@Override
public void afterSingletonsInstantiated() {
Assert.state(this.outputChannel != null || StringUtils.hasText(this.outputChannelName),
"'outputChannel' or 'outputChannelName' is required");
}
@Override
protected void onInit() {
if (this.getBeanFactory() != null) {
this.messagingTemplate.setBeanFactory(this.getBeanFactory());
}
}
/**
* Takes no action by default. Subclasses may override this if they
* need lifecycle-managed behavior. Protected by 'lifecycleLock'.
*/
@Override
protected void doStart() {
}
/**
* Takes no action by default. Subclasses may override this if they
* need lifecycle-managed behavior.
*/
@Override
protected void doStop() {
}
protected void sendMessage(Message<?> message) {
if (message == null) {
throw new MessagingException("cannot send a null message");
}
if (this.shouldTrack) {
message = MessageHistory.write(message, this, this.getMessageBuilderFactory());
}
try {
this.messagingTemplate.send(getOutputChannel(), message);
}
catch (RuntimeException e) {
if (!sendErrorMessageIfNecessary(message, e)) {
throw e;
}
}
}
/**
* Send an error message based on the exception and message.
* @param message the message.
* @param exception the exception.
* @return true if the error channel is available and message sent.
* @since 4.3.10
*/
protected final boolean sendErrorMessageIfNecessary(Message<?> message, RuntimeException exception) {
MessageChannel errorChannel = getErrorChannel();
if (errorChannel != null) {
this.messagingTemplate.send(errorChannel, buildErrorMessage(message, exception));
return true;
}
return false;
}
/**
* Build an error message for the exception and message using the configured
* {@link ErrorMessageStrategy}.
* @param message the message.
* @param exception the exception.
* @return the error message.
* @since 4.3.10
*/
protected final ErrorMessage buildErrorMessage(Message<?> message, RuntimeException exception) {
return this.errorMessageStrategy.buildErrorMessage(exception,
getErrorMessageAttributes(message));
}
/**
* Populate an {@link AttributeAccessor} to be used when building an error message
* with the {@link #setErrorMessageStrategy(ErrorMessageStrategy)
* errorMessageStrategy}.
* @param message the message.
* @return the attributes.
* @since 4.3.10
*/
protected AttributeAccessor getErrorMessageAttributes(Message<?> message) {
return ErrorMessageUtils.getAttributeAccessor(message, null);
}
}