/*
* 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.endpoint;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import org.springframework.context.Lifecycle;
import org.springframework.integration.channel.ExecutorChannelInterceptorAware;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.router.MessageRouter;
import org.springframework.integration.transaction.IntegrationResourceHolder;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.ExecutorChannelInterceptor;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* Message Endpoint that connects any {@link MessageHandler} implementation
* to a {@link PollableChannel}.
*
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
*/
public class PollingConsumer extends AbstractPollingEndpoint implements IntegrationConsumer {
private final PollableChannel inputChannel;
private final MessageHandler handler;
private final List<ChannelInterceptor> channelInterceptors;
private volatile long receiveTimeout = 1000;
public PollingConsumer(PollableChannel inputChannel, MessageHandler handler) {
Assert.notNull(inputChannel, "inputChannel must not be null");
Assert.notNull(handler, "handler must not be null");
this.inputChannel = inputChannel;
this.handler = handler;
if (this.inputChannel instanceof ExecutorChannelInterceptorAware) {
this.channelInterceptors = ((ExecutorChannelInterceptorAware) this.inputChannel).getChannelInterceptors();
}
else {
this.channelInterceptors = null;
}
}
public void setReceiveTimeout(long receiveTimeout) {
this.receiveTimeout = receiveTimeout;
}
@Override
public MessageChannel getInputChannel() {
return this.inputChannel;
}
@Override
public MessageChannel getOutputChannel() {
if (this.handler instanceof MessageProducer) {
return ((MessageProducer) this.handler).getOutputChannel();
}
else if (this.handler instanceof MessageRouter) {
return ((MessageRouter) this.handler).getDefaultOutputChannel();
}
else {
return null;
}
}
@Override
public MessageHandler getHandler() {
return this.handler;
}
@Override
protected void doStart() {
if (this.handler instanceof Lifecycle) {
((Lifecycle) this.handler).start();
}
super.doStart();
}
@Override
protected void doStop() {
if (this.handler instanceof Lifecycle) {
((Lifecycle) this.handler).stop();
}
super.doStop();
}
@Override
protected void handleMessage(Message<?> message) {
Message<?> theMessage = message;
Deque<ExecutorChannelInterceptor> interceptorStack = null;
try {
if (this.channelInterceptors != null
&& ((ExecutorChannelInterceptorAware) this.inputChannel).hasExecutorInterceptors()) {
interceptorStack = new ArrayDeque<ExecutorChannelInterceptor>();
theMessage = applyBeforeHandle(theMessage, interceptorStack);
if (theMessage == null) {
return;
}
}
this.handler.handleMessage(theMessage);
if (!CollectionUtils.isEmpty(interceptorStack)) {
triggerAfterMessageHandled(theMessage, null, interceptorStack);
}
}
catch (Exception ex) {
if (!CollectionUtils.isEmpty(interceptorStack)) {
triggerAfterMessageHandled(theMessage, ex, interceptorStack);
}
if (ex instanceof MessagingException) {
throw (MessagingException) ex;
}
String description = "Failed to handle " + theMessage + " to " + this + " in " + this.handler;
throw new MessageDeliveryException(theMessage, description, ex);
}
catch (Error ex) { //NOSONAR - ok, we re-throw below
if (!CollectionUtils.isEmpty(interceptorStack)) {
String description = "Failed to handle " + theMessage + " to " + this + " in " + this.handler;
triggerAfterMessageHandled(theMessage,
new MessageDeliveryException(theMessage, description, ex),
interceptorStack);
}
throw ex;
}
}
private Message<?> applyBeforeHandle(Message<?> message, Deque<ExecutorChannelInterceptor> interceptorStack) {
Message<?> theMessage = message;
for (ChannelInterceptor interceptor : this.channelInterceptors) {
if (interceptor instanceof ExecutorChannelInterceptor) {
ExecutorChannelInterceptor executorInterceptor = (ExecutorChannelInterceptor) interceptor;
theMessage = executorInterceptor.beforeHandle(theMessage, this.inputChannel, this.handler);
if (message == null) {
if (logger.isDebugEnabled()) {
logger.debug(executorInterceptor.getClass().getSimpleName()
+ " returned null from beforeHandle, i.e. precluding the send.");
}
triggerAfterMessageHandled(null, null, interceptorStack);
return null;
}
interceptorStack.add(executorInterceptor);
}
}
return theMessage;
}
private void triggerAfterMessageHandled(Message<?> message, Exception ex,
Deque<ExecutorChannelInterceptor> interceptorStack) {
Iterator<ExecutorChannelInterceptor> iterator = interceptorStack.descendingIterator();
while (iterator.hasNext()) {
ExecutorChannelInterceptor interceptor = iterator.next();
try {
interceptor.afterMessageHandled(message, this.inputChannel, this.handler, ex);
}
catch (Throwable ex2) { //NOSONAR
logger.error("Exception from afterMessageHandled in " + interceptor, ex2);
}
}
}
@Override
protected Message<?> receiveMessage() {
return (this.receiveTimeout >= 0)
? this.inputChannel.receive(this.receiveTimeout)
: this.inputChannel.receive();
}
@Override
protected Object getResourceToBind() {
return this.inputChannel;
}
@Override
protected String getResourceKey() {
return IntegrationResourceHolder.INPUT_CHANNEL;
}
}