/*
 * Copyright 2002-2019 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
 *
 *      https://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.config;

import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.SmartLifecycle;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.scheduling.PollerMetadata;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.core.BeanFactoryMessageChannelDestinationResolver;
import org.springframework.messaging.core.DestinationResolver;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * FactoryBean for creating a SourcePollingChannelAdapter instance.
 *
 * @author Mark Fisher
 * @author Oleg Zhurakousky
 * @author Gary Russell
 * @author Artem Bilan
 */
public class SourcePollingChannelAdapterFactoryBean implements FactoryBean<SourcePollingChannelAdapter>,
		BeanFactoryAware, BeanNameAware, BeanClassLoaderAware, InitializingBean, SmartLifecycle, DisposableBean {

	private final Object initializationMonitor = new Object();

	private MessageSource<?> source;

	private MessageChannel outputChannel;

	private String outputChannelName;

	private PollerMetadata pollerMetadata;

	private boolean autoStartup = true;

	private int phase = Integer.MAX_VALUE / 2;

	private Long sendTimeout;

	private String beanName;

	private ConfigurableBeanFactory beanFactory;

	private ClassLoader beanClassLoader;

	private DestinationResolver<MessageChannel> channelResolver;

	private String role;

	private volatile SourcePollingChannelAdapter adapter;

	private volatile boolean initialized;


	public void setSource(MessageSource<?> source) {
		this.source = source;
	}

	public void setSendTimeout(long sendTimeout) {
		this.sendTimeout = sendTimeout;
	}

	public void setOutputChannel(MessageChannel outputChannel) {
		this.outputChannel = outputChannel;
	}

	public void setOutputChannelName(String outputChannelName) {
		this.outputChannelName = outputChannelName;
	}

	public void setPollerMetadata(PollerMetadata pollerMetadata) {
		this.pollerMetadata = pollerMetadata;
	}

	public void setAutoStartup(boolean autoStartup) {
		this.autoStartup = autoStartup;
	}

	public void setPhase(int phase) {
		this.phase = phase;
	}

	public void setRole(String role) {
		this.role = role;
	}

	/**
	 * Specify the {@link DestinationResolver} strategy to use.
	 * The default is a BeanFactoryChannelResolver.
	 * @param channelResolver The channel resolver.
	 * @since 4.1.3
	 */
	public void setChannelResolver(DestinationResolver<MessageChannel> channelResolver) {
		Assert.notNull(channelResolver, "'channelResolver' must not be null");
		this.channelResolver = channelResolver;
	}

	@Override
	public void setBeanFactory(BeanFactory beanFactory) {
		Assert.isInstanceOf(ConfigurableBeanFactory.class, beanFactory,
				"a ConfigurableBeanFactory is required");
		this.beanFactory = (ConfigurableBeanFactory) beanFactory;
	}

	@Override
	public void setBeanClassLoader(ClassLoader classLoader) {
		this.beanClassLoader = classLoader;
	}

	@Override
	public void setBeanName(String beanName) {
		this.beanName = beanName;
	}

	@Override
	public void afterPropertiesSet() {
		if (this.channelResolver == null) {
			this.channelResolver = new BeanFactoryMessageChannelDestinationResolver(this.beanFactory);
		}
		initializeAdapter();
	}

	@Override
	public SourcePollingChannelAdapter getObject() {
		if (this.adapter == null) {
			initializeAdapter();
		}
		return this.adapter;
	}

	@Override
	public Class<?> getObjectType() {
		return SourcePollingChannelAdapter.class;
	}

	private void initializeAdapter() {
		synchronized (this.initializationMonitor) {
			if (this.initialized) {
				return;
			}
			Assert.notNull(this.source, "source is required");

			SourcePollingChannelAdapter spca = new SourcePollingChannelAdapter();
			spca.setSource(this.source);

			if (StringUtils.hasText(this.outputChannelName)) {
				Assert.isNull(this.outputChannel, "'outputChannelName' and 'outputChannel' are mutually exclusive.");
				spca.setOutputChannelName(this.outputChannelName);
			}
			else {
				Assert.notNull(this.outputChannel, "outputChannel is required");
				spca.setOutputChannel(this.outputChannel);
			}

			if (this.pollerMetadata == null) {
				this.pollerMetadata = PollerMetadata.getDefaultPollerMetadata(this.beanFactory);
				Assert.notNull(this.pollerMetadata, "No poller has been defined for channel-adapter '"
						+ this.beanName + "', and no default poller is available within the context.");
			}
			if (this.pollerMetadata.getMaxMessagesPerPoll() == Integer.MIN_VALUE) {
				// the default is 1 since a source might return
				// a non-null and non-interruptible value every time it is invoked
				this.pollerMetadata.setMaxMessagesPerPoll(1);
			}
			spca.setMaxMessagesPerPoll(this.pollerMetadata.getMaxMessagesPerPoll());
			if (this.sendTimeout != null) {
				spca.setSendTimeout(this.sendTimeout);
			}
			spca.setTaskExecutor(this.pollerMetadata.getTaskExecutor());
			spca.setAdviceChain(this.pollerMetadata.getAdviceChain());
			spca.setTrigger(this.pollerMetadata.getTrigger());
			spca.setErrorHandler(this.pollerMetadata.getErrorHandler());
			spca.setBeanClassLoader(this.beanClassLoader);
			spca.setAutoStartup(this.autoStartup);
			spca.setPhase(this.phase);
			spca.setRole(this.role);
			spca.setBeanName(this.beanName);
			spca.setBeanFactory(this.beanFactory);
			spca.setTransactionSynchronizationFactory(this.pollerMetadata.getTransactionSynchronizationFactory());
			spca.afterPropertiesSet();
			this.adapter = spca;
			this.initialized = true;
		}
	}

	/*
	 * SmartLifecycle implementation (delegates to the created adapter)
	 */

	@Override
	public boolean isAutoStartup() {
		return (this.adapter == null) || this.adapter.isAutoStartup();
	}

	@Override
	public int getPhase() {
		return (this.adapter != null) ? this.adapter.getPhase() : 0;
	}

	@Override
	public boolean isRunning() {
		return (this.adapter != null) && this.adapter.isRunning();
	}

	@Override
	public void start() {
		if (this.adapter != null) {
			this.adapter.start();
		}
	}

	@Override
	public void stop() {
		if (this.adapter != null) {
			this.adapter.stop();
		}
	}

	@Override
	public void stop(Runnable callback) {
		if (this.adapter != null) {
			this.adapter.stop(callback);
		}
		else {
			callback.run();
		}
	}

	@Override
	public void destroy() {
		if (this.adapter != null) {
			this.adapter.destroy();
		}
	}

}
