/* * 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 java.util.Collection; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.Executor; import java.util.concurrent.ScheduledFuture; import java.util.stream.Collectors; import org.aopalliance.aop.Advice; import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.core.task.SyncTaskExecutor; import org.springframework.integration.channel.MessagePublishingErrorHandler; import org.springframework.integration.support.MessagingExceptionWrapper; import org.springframework.integration.support.channel.BeanFactoryChannelResolver; import org.springframework.integration.transaction.ExpressionEvaluatingTransactionSynchronizationProcessor; import org.springframework.integration.transaction.IntegrationResourceHolder; import org.springframework.integration.transaction.IntegrationResourceHolderSynchronization; import org.springframework.integration.transaction.PassThroughTransactionSynchronizationFactory; import org.springframework.integration.transaction.TransactionSynchronizationFactory; import org.springframework.integration.util.ErrorHandlingTaskExecutor; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessagingException; import org.springframework.scheduling.Trigger; import org.springframework.scheduling.support.PeriodicTrigger; import org.springframework.transaction.interceptor.TransactionInterceptor; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.ErrorHandler; /** * @author Mark Fisher * @author Oleg Zhurakousky * @author Gary Russell * @author Artem Bilan * @author Andreas Baer */ public abstract class AbstractPollingEndpoint extends AbstractEndpoint implements BeanClassLoaderAware { private volatile Executor taskExecutor = new SyncTaskExecutor(); private volatile ErrorHandler errorHandler; private volatile boolean errorHandlerIsDefault; private volatile Trigger trigger = new PeriodicTrigger(10); private volatile List<Advice> adviceChain; private volatile ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); private volatile ScheduledFuture<?> runningTask; private volatile Runnable poller; private volatile boolean initialized; private volatile long maxMessagesPerPoll = -1; private final Object initializationMonitor = new Object(); private volatile TransactionSynchronizationFactory transactionSynchronizationFactory; public AbstractPollingEndpoint() { this.setPhase(Integer.MAX_VALUE / 2); } public void setTaskExecutor(Executor taskExecutor) { this.taskExecutor = (taskExecutor != null ? taskExecutor : new SyncTaskExecutor()); } public void setTrigger(Trigger trigger) { this.trigger = (trigger != null ? trigger : new PeriodicTrigger(10)); } public void setAdviceChain(List<Advice> adviceChain) { this.adviceChain = adviceChain; } public void setMaxMessagesPerPoll(long maxMessagesPerPoll) { this.maxMessagesPerPoll = maxMessagesPerPoll; } public void setErrorHandler(ErrorHandler errorHandler) { this.errorHandler = errorHandler; } @Override public void setBeanClassLoader(ClassLoader classLoader) { this.beanClassLoader = classLoader; } public void setTransactionSynchronizationFactory(TransactionSynchronizationFactory transactionSynchronizationFactory) { this.transactionSynchronizationFactory = transactionSynchronizationFactory; } /** * Return the default error channel if the error handler is explicitly provided and * it is a {@link MessagePublishingErrorHandler}. * @return the channel or null. * @since 4.3 */ public MessageChannel getDefaultErrorChannel() { if (!this.errorHandlerIsDefault && this.errorHandler instanceof MessagePublishingErrorHandler) { return ((MessagePublishingErrorHandler) this.errorHandler).getDefaultErrorChannel(); } else { return null; } } protected ClassLoader getBeanClassLoader() { return this.beanClassLoader; } /** * Return true if this advice should be applied only to the {@link #receiveMessage()} operation * rather than the whole poll. * @param advice The advice. * @return true to only advise the receive operation. */ protected boolean isReceiveOnlyAdvice(Advice advice) { return false; } /** * Add the advice chain to the component that responds to {@link #receiveMessage()} calls. * @param chain the advice chain {@code Collection}. */ protected void applyReceiveOnlyAdviceChain(Collection<Advice> chain) { } @Override protected void onInit() { synchronized (this.initializationMonitor) { if (this.initialized) { return; } Assert.notNull(this.trigger, "Trigger is required"); if (this.taskExecutor != null) { if (!(this.taskExecutor instanceof ErrorHandlingTaskExecutor)) { if (this.errorHandler == null) { Assert.notNull(this.getBeanFactory(), "BeanFactory is required"); this.errorHandler = new MessagePublishingErrorHandler( new BeanFactoryChannelResolver(getBeanFactory())); this.errorHandlerIsDefault = true; } this.taskExecutor = new ErrorHandlingTaskExecutor(this.taskExecutor, this.errorHandler); } } if (this.transactionSynchronizationFactory == null && this.adviceChain != null) { if (this.adviceChain.stream().anyMatch(TransactionInterceptor.class::isInstance)) { this.transactionSynchronizationFactory = new PassThroughTransactionSynchronizationFactory(); } } this.initialized = true; } } @SuppressWarnings("unchecked") private Runnable createPoller() throws Exception { List<Advice> receiveOnlyAdviceChain = null; if (!CollectionUtils.isEmpty(this.adviceChain)) { receiveOnlyAdviceChain = this.adviceChain.stream() .filter(this::isReceiveOnlyAdvice) .collect(Collectors.toList()); } Callable<Boolean> pollingTask = this::doPoll; List<Advice> adviceChain = this.adviceChain; if (!CollectionUtils.isEmpty(adviceChain)) { ProxyFactory proxyFactory = new ProxyFactory(pollingTask); if (!CollectionUtils.isEmpty(adviceChain)) { adviceChain.stream() .filter(advice -> !isReceiveOnlyAdvice(advice)) .forEach(proxyFactory::addAdvice); } pollingTask = (Callable<Boolean>) proxyFactory.getProxy(this.beanClassLoader); } if (!CollectionUtils.isEmpty(receiveOnlyAdviceChain)) { applyReceiveOnlyAdviceChain(receiveOnlyAdviceChain); } return new Poller(pollingTask); } // LifecycleSupport implementation @Override // guarded by super#lifecycleLock protected void doStart() { if (!this.initialized) { this.onInit(); } Assert.state(this.getTaskScheduler() != null, "unable to start polling, no taskScheduler available"); try { this.poller = createPoller(); } catch (Exception e) { this.initialized = false; throw new MessagingException("Failed to create Poller", e); } this.runningTask = this.getTaskScheduler().schedule(this.poller, this.trigger); } @Override // guarded by super#lifecycleLock protected void doStop() { if (this.runningTask != null) { this.runningTask.cancel(true); } this.runningTask = null; this.initialized = false; } private boolean doPoll() { IntegrationResourceHolder holder = this.bindResourceHolderIfNecessary( this.getResourceKey(), this.getResourceToBind()); Message<?> message = null; try { message = this.receiveMessage(); } catch (Exception e) { if (Thread.interrupted()) { if (logger.isDebugEnabled()) { logger.debug("Poll interrupted - during stop()? : " + e.getMessage()); } return false; } else { throw (RuntimeException) e; } } boolean result; if (message == null) { if (this.logger.isDebugEnabled()) { this.logger.debug("Received no Message during the poll, returning 'false'"); } result = false; } else { if (this.logger.isDebugEnabled()) { this.logger.debug("Poll resulted in Message: " + message); } if (holder != null) { holder.setMessage(message); } try { this.handleMessage(message); } catch (Exception e) { if (e instanceof MessagingException) { throw new MessagingExceptionWrapper(message, (MessagingException) e); } else { throw new MessagingException(message, e); } } result = true; } return result; } /** * Obtain the next message (if one is available). MAY return null * if no message is immediately available. * @return The message or null. */ protected abstract Message<?> receiveMessage(); /** * Handle a message. * @param message The message. */ protected abstract void handleMessage(Message<?> message); /** * Return a resource (MessageSource etc) to bind when using transaction * synchronization. * @return The resource, or null if transaction synchronization is not required. */ protected Object getResourceToBind() { return null; } /** * Return the key under which the resource will be made available as an * attribute on the {@link IntegrationResourceHolder}. The default * {@link ExpressionEvaluatingTransactionSynchronizationProcessor} * makes this attribute available as a variable in SpEL expressions. * @return The key, or null (default) if the resource shouldn't be * made available as a attribute. */ protected String getResourceKey() { return null; } private IntegrationResourceHolder bindResourceHolderIfNecessary(String key, Object resource) { if (this.transactionSynchronizationFactory != null && resource != null && TransactionSynchronizationManager.isActualTransactionActive()) { TransactionSynchronization synchronization = this.transactionSynchronizationFactory.create(resource); if (synchronization != null) { TransactionSynchronizationManager.registerSynchronization(synchronization); if (synchronization instanceof IntegrationResourceHolderSynchronization) { IntegrationResourceHolderSynchronization integrationSynchronization = ((IntegrationResourceHolderSynchronization) synchronization); integrationSynchronization.setShouldUnbindAtCompletion(false); if (!TransactionSynchronizationManager.hasResource(resource)) { TransactionSynchronizationManager.bindResource(resource, integrationSynchronization.getResourceHolder()); } } } Object resourceHolder = TransactionSynchronizationManager.getResource(resource); if (resourceHolder instanceof IntegrationResourceHolder) { IntegrationResourceHolder integrationResourceHolder = (IntegrationResourceHolder) resourceHolder; if (key != null) { integrationResourceHolder.addAttribute(key, resource); } return integrationResourceHolder; } } return null; } /** * Default Poller implementation */ private final class Poller implements Runnable { private final Callable<Boolean> pollingTask; Poller(Callable<Boolean> pollingTask) { this.pollingTask = pollingTask; } @Override public void run() { AbstractPollingEndpoint.this.taskExecutor.execute(() -> { int count = 0; while (AbstractPollingEndpoint.this.initialized && (AbstractPollingEndpoint.this.maxMessagesPerPoll <= 0 || count < AbstractPollingEndpoint.this.maxMessagesPerPoll)) { try { if (!Poller.this.pollingTask.call()) { break; } count++; } catch (Exception e) { if (e instanceof MessagingException) { throw (MessagingException) e; } else { Message<?> failedMessage = null; if (AbstractPollingEndpoint.this.transactionSynchronizationFactory != null) { Object resource = TransactionSynchronizationManager.getResource(getResourceToBind()); if (resource instanceof IntegrationResourceHolder) { failedMessage = ((IntegrationResourceHolder) resource).getMessage(); } } throw new MessagingException(failedMessage, e); } } finally { if (AbstractPollingEndpoint.this.transactionSynchronizationFactory != null) { Object resource = getResourceToBind(); if (TransactionSynchronizationManager.hasResource(resource)) { TransactionSynchronizationManager.unbindResource(resource); } } } } }); } } }