/*
* Copyright 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.support;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.core.AttributeAccessor;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.support.channel.BeanFactoryChannelResolver;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.core.DestinationResolver;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.util.Assert;
/**
* The component which can be used as general purpose of errors publishing.
* Can be called or extended in any error handling or retry scenarios.
* <p>
* An {@link ErrorMessageStrategy} can be used to provide customization for the target
* {@link ErrorMessage} based on the {@link AttributeAccessor} (or the message and/or
* throwable when using the other {@code publish()} methods).
*
* @author Artem Bilan
* @author Gary Russell
*
* @since 4.3.10
*/
public class ErrorMessagePublisher implements BeanFactoryAware {
protected final Log logger = LogFactory.getLog(getClass());
protected final MessagingTemplate messagingTemplate = new MessagingTemplate();
private DestinationResolver<MessageChannel> channelResolver;
private MessageChannel channel;
private String channelName;
private ErrorMessageStrategy errorMessageStrategy = new DefaultErrorMessageStrategy();
public final void setErrorMessageStrategy(ErrorMessageStrategy errorMessageStrategy) {
Assert.notNull(errorMessageStrategy, "'errorMessageStrategy' must not be null");
this.errorMessageStrategy = errorMessageStrategy;
}
public final void setChannel(MessageChannel channel) {
this.channel = channel;
}
public void setChannelName(String channelName) {
this.channelName = channelName;
}
public ErrorMessageStrategy getErrorMessageStrategy() {
return this.errorMessageStrategy;
}
public MessageChannel getChannel() {
populateChannel();
return this.channel;
}
public final void setSendTimeout(long sendTimeout) {
this.messagingTemplate.setSendTimeout(sendTimeout);
}
public final void setChannelResolver(DestinationResolver<MessageChannel> channelResolver) {
Assert.notNull(channelResolver, "channelResolver must not be null");
this.channelResolver = channelResolver;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
Assert.notNull(beanFactory, "beanFactory must not be null");
if (this.channelResolver == null) {
this.channelResolver = new BeanFactoryChannelResolver(beanFactory);
}
}
protected MessagingTemplate getMessagingTemplate() {
return this.messagingTemplate;
}
protected DestinationResolver<MessageChannel> getChannelResolver() {
return this.channelResolver;
}
/**
* Publish an error message for the supplied exception.
* @param exception the exception.
*/
public void publish(MessagingException exception) {
publish(null, exception.getFailedMessage(), exception);
}
/**
* Publish an error message for the supplied message and throwable. If the throwable
* is already a {@link MessagingException} containing the message in its
* {@code failedMessage} property, use {@link #publish(MessagingException)} instead.
* @param failedMessage the message.
* @param throwable the throwable.
*/
public void publish(Message<?> failedMessage, Throwable throwable) {
publish(null, failedMessage, throwable);
}
/**
* Publish an error message for the supplied exception.
* @param inputMessage the message that started the subflow.
* @param exception the exception.
*/
public void publish(Message<?> inputMessage, MessagingException exception) {
publish(inputMessage, exception.getFailedMessage(), exception);
}
/**
* Publish an error message for the supplied message and throwable. If the throwable
* is already a {@link MessagingException} containing the message in its
* {@code failedMessage} property, use {@link #publish(MessagingException)} instead.
* @param inputMessage the message that started the subflow.
* @param failedMessage the message.
* @param throwable the throwable.
*/
public void publish(Message<?> inputMessage, Message<?> failedMessage, Throwable throwable) {
publish(throwable, ErrorMessageUtils.getAttributeAccessor(inputMessage, failedMessage));
}
/**
* Publish an error message for the supplied throwable and context.
* The {@link #errorMessageStrategy} is used to build a {@link ErrorMessage}
* to publish.
* @param throwable the throwable. May be null.
* @param context the context for {@link ErrorMessage} properties.
*/
public void publish(Throwable throwable, AttributeAccessor context) {
populateChannel();
Throwable payload = determinePayload(throwable, context);
ErrorMessage errorMessage = this.errorMessageStrategy.buildErrorMessage(payload, context);
if (this.logger.isDebugEnabled() && payload instanceof MessagingException) {
MessagingException exception = (MessagingException) errorMessage.getPayload();
this.logger.debug("Sending ErrorMessage: failedMessage: " + exception.getFailedMessage(), exception);
}
this.messagingTemplate.send(errorMessage);
}
/**
* Build a {@code Throwable payload} for future {@link ErrorMessage}.
* @param throwable the error to determine an {@link ErrorMessage} payload. Can be null.
* @param context the context for error.
* @return the throwable for the {@link ErrorMessage} payload
* @see ErrorMessageUtils
*/
protected Throwable determinePayload(Throwable throwable, AttributeAccessor context) {
Throwable lastThrowable = throwable;
if (lastThrowable == null) {
lastThrowable = payloadWhenNull(context);
}
else if (!(lastThrowable instanceof MessagingException)) {
lastThrowable = new MessagingException(
(Message<?>) context.getAttribute(ErrorMessageUtils.FAILED_MESSAGE_CONTEXT_KEY),
lastThrowable.getMessage(), lastThrowable);
}
return lastThrowable;
}
/**
* Build a {@code Throwable payload} based on the provided context
* for future {@link ErrorMessage} when there is original {@code Throwable}.
* @param context the {@link AttributeAccessor} to use for exception properties.
* @return the {@code Throwable} for an {@link ErrorMessage} payload.
* @see ErrorMessageUtils
*/
protected Throwable payloadWhenNull(AttributeAccessor context) {
return new MessagingException((Message<?>) context.getAttribute(ErrorMessageUtils.FAILED_MESSAGE_CONTEXT_KEY),
"No root cause exception available");
}
private void populateChannel() {
if (this.messagingTemplate.getDefaultDestination() == null) {
if (this.channel == null) {
String recoveryChannelName = this.channelName;
if (recoveryChannelName == null) {
recoveryChannelName = IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME;
}
if (this.channelResolver != null) {
this.channel = this.channelResolver.resolveDestination(recoveryChannelName);
}
}
this.messagingTemplate.setDefaultChannel(this.channel);
}
}
}