/*
 * Copyright 2010-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.data.gemfire.client;

import static java.util.stream.StreamSupport.stream;
import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeCollection;
import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeIterable;
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException;
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;

import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import org.apache.geode.cache.client.ClientCache;
import org.apache.geode.cache.client.Pool;
import org.apache.geode.cache.client.PoolFactory;
import org.apache.geode.cache.client.PoolManager;
import org.apache.geode.cache.query.QueryService;
import org.apache.geode.distributed.DistributedSystem;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.gemfire.GemfireUtils;
import org.springframework.data.gemfire.config.annotation.PoolConfigurer;
import org.springframework.data.gemfire.support.AbstractFactoryBeanSupport;
import org.springframework.data.gemfire.support.ConnectionEndpoint;
import org.springframework.data.gemfire.support.ConnectionEndpointList;
import org.springframework.data.gemfire.util.DistributedSystemUtils;
import org.springframework.util.StringUtils;

/**
 * Spring {@link FactoryBean} to construct, configure and initialize a {@link Pool}.
 *
 * If a new {@link Pool} is created, its lifecycle is bound to that of this declaring {@link FactoryBean}
 * and indirectly, the Spring container.
 *
 * If a {@link Pool} having the configured {@link String name} already exists, then the existing {@link Pool}
 * will be returned as is without any modifications and its lifecycle will be unaffected by this {@link FactoryBean}.
 *
 * @author Costin Leau
 * @author John Blum
 * @see java.net.InetSocketAddress
 * @see org.apache.geode.cache.client.ClientCache
 * @see org.apache.geode.cache.client.Pool
 * @see org.apache.geode.cache.client.PoolFactory
 * @see org.apache.geode.cache.client.PoolManager
 * @see org.apache.geode.distributed.DistributedSystem
 * @see org.springframework.beans.factory.DisposableBean
 * @see org.springframework.beans.factory.InitializingBean
 * @see org.springframework.data.gemfire.config.annotation.PoolConfigurer
 * @see org.springframework.data.gemfire.support.AbstractFactoryBeanSupport
 * @see org.springframework.data.gemfire.support.ConnectionEndpoint
 * @see org.springframework.data.gemfire.support.ConnectionEndpointList
 */
@SuppressWarnings("unused")
public class PoolFactoryBean extends AbstractFactoryBeanSupport<Pool> implements DisposableBean, InitializingBean {

	protected static final int DEFAULT_LOCATOR_PORT = DistributedSystemUtils.DEFAULT_LOCATOR_PORT;
	protected static final int DEFAULT_SERVER_PORT = DistributedSystemUtils.DEFAULT_CACHE_SERVER_PORT;

	// Indicates whether the Pool has been created by this FactoryBean, or not
	volatile boolean springManagedPool = true;

	// GemFire Pool Configuration Settings
	private boolean keepAlive = false;
	private boolean multiUserAuthentication = PoolFactory.DEFAULT_MULTIUSER_AUTHENTICATION;
	private boolean prSingleHopEnabled = PoolFactory.DEFAULT_PR_SINGLE_HOP_ENABLED;
	private boolean subscriptionEnabled = PoolFactory.DEFAULT_SUBSCRIPTION_ENABLED;
	private boolean threadLocalConnections = PoolFactory.DEFAULT_THREAD_LOCAL_CONNECTIONS;

	private int freeConnectionTimeout = PoolFactory.DEFAULT_FREE_CONNECTION_TIMEOUT;
	private int loadConditioningInterval = PoolFactory.DEFAULT_LOAD_CONDITIONING_INTERVAL;
	private int maxConnections = PoolFactory.DEFAULT_MAX_CONNECTIONS;
	private int minConnections = PoolFactory.DEFAULT_MIN_CONNECTIONS;
	private int readTimeout = PoolFactory.DEFAULT_READ_TIMEOUT;
	private int retryAttempts = PoolFactory.DEFAULT_RETRY_ATTEMPTS;
	private int socketBufferSize = PoolFactory.DEFAULT_SOCKET_BUFFER_SIZE;
	private int socketConnectTimeout = PoolFactory.DEFAULT_SOCKET_CONNECT_TIMEOUT;
	private int statisticInterval = PoolFactory.DEFAULT_STATISTIC_INTERVAL;
	private int subscriptionAckInterval = PoolFactory.DEFAULT_SUBSCRIPTION_ACK_INTERVAL;
	private int subscriptionMessageTrackingTimeout = PoolFactory.DEFAULT_SUBSCRIPTION_MESSAGE_TRACKING_TIMEOUT;
	private int subscriptionRedundancy = PoolFactory.DEFAULT_SUBSCRIPTION_REDUNDANCY;
	private int subscriptionTimeoutMultiplier = PoolFactory.DEFAULT_SUBSCRIPTION_TIMEOUT_MULTIPLIER;

	private long idleTimeout = PoolFactory.DEFAULT_IDLE_TIMEOUT;
	private long pingInterval = PoolFactory.DEFAULT_PING_INTERVAL;

	private ConnectionEndpointList locators = new ConnectionEndpointList();
	private ConnectionEndpointList servers = new ConnectionEndpointList();

	private List<PoolConfigurer> poolConfigurers = Collections.emptyList();

	private volatile Pool pool;

	private PoolConfigurer compositePoolConfigurer = (beanName, bean) ->
		nullSafeCollection(poolConfigurers).forEach(poolConfigurer -> poolConfigurer.configure(beanName, bean));

	private PoolFactoryInitializer poolFactoryInitializer;

	private String name;
	private String serverGroup = PoolFactory.DEFAULT_SERVER_GROUP;

	/**
	 * Prepares the construction, configuration and initialization of a new {@link Pool}.
	 *
	 * @throws Exception if {@link Pool} initialization fails.
	 * @see org.apache.geode.cache.client.PoolManager
	 * @see org.apache.geode.cache.client.PoolFactory
	 * @see org.apache.geode.cache.client.Pool
	 */
	@Override
	public void afterPropertiesSet() throws Exception {
		init(Optional.ofNullable(find(resolvePoolName())));
	}

	@SuppressWarnings("all")
	private void init(Optional<Pool> existingPool) {

		if (existingPool.isPresent()) {

			this.pool = existingPool.get();
			this.springManagedPool = false;

			logDebug(() -> String.format("A Pool with name [%s] already exists; Using existing Pool",
				this.pool.getName()));

			logDebug("PoolConfigurers will not be applied");
		}
		else {
			logDebug("Pool [%s] not found; Lazily creating new Pool...", getName());
			applyPoolConfigurers();
		}
	}

	private String resolvePoolName() {

		if (!StringUtils.hasText(getName())) {
			setName(Optional.ofNullable(getBeanName())
				.filter(StringUtils::hasText)
				.orElseThrow(() -> newIllegalArgumentException("Pool name is required")));
		}

		return getName();
	}

	private Pool find(String name) {
		return PoolManager.find(name);
	}

	private void applyPoolConfigurers() {
		applyPoolConfigurers(getCompositePoolConfigurer());
	}

	/**
	 * Null-safe operation to apply the given array of {@link PoolConfigurer PoolConfigurers}
	 * to this {@link PoolFactoryBean}.
	 *
	 * @param poolConfigurers array of {@link PoolConfigurer PoolConfigurers} applied to this {@link PoolFactoryBean}.
	 * @see org.springframework.data.gemfire.config.annotation.PoolConfigurer
	 * @see #applyPoolConfigurers(Iterable)
	 */
	protected void applyPoolConfigurers(PoolConfigurer... poolConfigurers) {
		applyPoolConfigurers(Arrays.asList(nullSafeArray(poolConfigurers, PoolConfigurer.class)));
	}

	/**
	 * Null-safe operation to apply the given {@link Iterable} of {@link PoolConfigurer PoolConfigurers}
	 * to this {@link PoolFactoryBean}.
	 *
	 * @param poolConfigurers {@link Iterable} of {@link PoolConfigurer PoolConfigurers}
	 * applied to this {@link PoolFactoryBean}.
	 * @see org.springframework.data.gemfire.config.annotation.PoolConfigurer
	 */
	protected void applyPoolConfigurers(Iterable<PoolConfigurer> poolConfigurers) {
		stream(nullSafeIterable(poolConfigurers).spliterator(), false)
			.forEach(poolConfigurer -> poolConfigurer.configure(getName(), this));
	}

	/**
	 * Releases all system resources and destroys the {@link Pool} when created by this {@link PoolFactoryBean}.
	 *
	 * @throws Exception if the {@link Pool} destruction caused an error.
	 * @see org.springframework.beans.factory.DisposableBean#destroy()
	 */
	@Override
	public void destroy() throws Exception {

		Optional.ofNullable(this.pool)
			.filter(pool -> this.springManagedPool)
			.filter(pool -> !pool.isDestroyed())
			.ifPresent(pool -> {
				pool.releaseThreadLocalConnection();
				pool.destroy(this.keepAlive);
				setPool(null);
				logDebug("Destroyed Pool [%s]", pool.getName());
			});
	}

	/**
	 * Returns an object reference to the {@link Pool} created by this {@link PoolFactoryBean}.
	 *
	 * @return an object reference to the {@link Pool} created by this {@link PoolFactoryBean}.
	 * @see org.springframework.beans.factory.FactoryBean#getObject()
	 * @see org.apache.geode.cache.client.Pool
	 */
	@Override
	public Pool getObject() throws Exception {

		return Optional.ofNullable(this.pool).orElseGet(() -> {

			eagerlyInitializeClientCache();

			Pool namedPool = find(getName());

			this.pool = namedPool != null ? namedPool
				: postProcess(create(postProcess(configure(initialize(createPoolFactory()))), getName()));

			return this.pool;
		});
	}

	/**
	 * Attempts to eagerly initialize the {@link ClientCache} if not already present so that a single
	 * {@link DistributedSystem} will exist, which is required to create a {@link Pool} instance.
	 *
	 * @see org.springframework.beans.factory.BeanFactory#getBean(Class)
	 * @see org.apache.geode.cache.client.ClientCache
	 * @see org.apache.geode.distributed.DistributedSystem
	 * @see #isClientCachePresent()
	 */
	private void eagerlyInitializeClientCache() {

		if (!isClientCachePresent()) {
			getBeanFactory().getBean(ClientCache.class);
		}
	}

	/**
	 * Determines whether the {@link ClientCache} exists yet or not.
	 *
	 * @return a boolean value indicating whether the single {@link ClientCache} instance
	 * has been created yet.
	 * @see org.springframework.data.gemfire.GemfireUtils#getClientCache()
	 * @see org.apache.geode.distributed.DistributedSystem
	 * @see org.apache.geode.cache.client.ClientCache
	 */
	boolean isClientCachePresent() {

		return Optional.ofNullable(GemfireUtils.getClientCache())
			.filter(clientCache -> !clientCache.isClosed())
			.map(ClientCache::getDistributedSystem)
			.filter(GemfireUtils::isConnected)
			.isPresent();
	}

	/**
	 * Creates an instance of the {@link PoolFactory} interface to construct, configure and initialize a {@link Pool}.
	 *
	 * @return a {@link PoolFactory} implementation to create a {@link Pool}.
	 * @see org.apache.geode.cache.client.PoolManager#createFactory()
	 * @see org.apache.geode.cache.client.PoolFactory
	 */
	protected PoolFactory createPoolFactory() {
		return PoolManager.createFactory();
	}

	/**
	 * Configures the given {@link PoolFactory} from this {@link PoolFactoryBean}.
	 *
	 * @param poolFactory {@link PoolFactory} to configure.
	 * @return the given {@link PoolFactory}.
	 * @see org.apache.geode.cache.client.PoolFactory
	 */
	protected PoolFactory configure(PoolFactory poolFactory) {

		Optional.ofNullable(poolFactory).ifPresent(it -> {

			it.setFreeConnectionTimeout(this.freeConnectionTimeout);
			it.setIdleTimeout(this.idleTimeout);
			it.setLoadConditioningInterval(this.loadConditioningInterval);
			it.setMaxConnections(this.maxConnections);
			it.setMinConnections(this.minConnections);
			it.setMultiuserAuthentication(this.multiUserAuthentication);
			it.setPingInterval(this.pingInterval);
			it.setPRSingleHopEnabled(this.prSingleHopEnabled);
			it.setReadTimeout(this.readTimeout);
			it.setRetryAttempts(this.retryAttempts);
			it.setServerGroup(this.serverGroup);
			it.setSocketBufferSize(this.socketBufferSize);
			it.setSocketConnectTimeout(this.socketConnectTimeout);
			it.setStatisticInterval(this.statisticInterval);
			it.setSubscriptionAckInterval(this.subscriptionAckInterval);
			it.setSubscriptionEnabled(this.subscriptionEnabled);
			it.setSubscriptionMessageTrackingTimeout(this.subscriptionMessageTrackingTimeout);
			it.setSubscriptionRedundancy(this.subscriptionRedundancy);
			it.setSubscriptionTimeoutMultiplier(this.subscriptionTimeoutMultiplier);
			it.setThreadLocalConnections(this.threadLocalConnections);

			nullSafeCollection(this.locators).forEach(locator ->
				it.addLocator(locator.getHost(), locator.getPort()));

			nullSafeCollection(this.servers).forEach(server ->
				it.addServer(server.getHost(), server.getPort()));
		});

		return poolFactory;
	}

	/**
	 * Initializes the given {@link PoolFactory} with any configured {@link PoolFactoryInitializer}.
	 *
	 * @param poolFactory {@link PoolFactory} to initialize.
	 * @return the initialized {@link PoolFactory}.
	 * @see org.apache.geode.cache.client.PoolFactory
	 */
	protected PoolFactory initialize(PoolFactory poolFactory) {

		return Optional.ofNullable(this.poolFactoryInitializer)
			.map(initializer -> initializer.initialize(poolFactory))
			.orElse(poolFactory);
	}

	/**
	 * Post processes the fully configured {@link PoolFactory}.
	 *
	 * @param poolFactory {@link PoolFactory} to post process.
	 * @return the post processed {@link PoolFactory}.
	 * @see org.apache.geode.cache.client.PoolFactory
	 */
	protected PoolFactory postProcess(PoolFactory poolFactory) {
		return poolFactory;
	}

	/**
	 * Creates a {@link Pool} with the given {@link String name} using the provided {@link PoolFactory}.
	 *
	 * @param poolFactory {@link PoolFactory} used to create the {@link Pool}.
	 * @param poolName {@link String name} of the new {@link Pool}.
	 * @return a new instance of {@link Pool} with the given {@link String name}.
	 * @see org.apache.geode.cache.client.PoolFactory#create(String)
	 * @see org.apache.geode.cache.client.Pool
	 */
	protected Pool create(PoolFactory poolFactory, String poolName) {
		return poolFactory.create(poolName);
	}

	/**
	 * Post processes the {@link Pool} created by this {@link PoolFactoryBean}.
	 *
	 * @param pool {@link Pool} to post process.
	 * @return the post processed {@link Pool}.
	 * @see org.apache.geode.cache.client.Pool
	 */
	protected Pool postProcess(Pool pool) {
		return pool;
	}

	/**
	 * Returns the {@link Class type} of {@link Pool} produced by this {@link PoolFactoryBean}.
	 *
	 * @return the {@link Class type} of {@link Pool} produced by this {@link PoolFactoryBean}.
	 * @see org.springframework.beans.factory.FactoryBean#getObjectType()
	 */
	@Override
	@SuppressWarnings("unchecked")
	public Class<?> getObjectType() {
		return this.pool != null ? this.pool.getClass() : Pool.class;
	}

	public void addLocators(ConnectionEndpoint... locators) {
		this.locators.add(locators);
	}

	public void addLocators(Iterable<ConnectionEndpoint> locators) {
		this.locators.add(locators);
	}

	public void addServers(ConnectionEndpoint... servers) {
		this.servers.add(servers);
	}

	public void addServers(Iterable<ConnectionEndpoint> servers) {
		this.servers.add(servers);
	}

	/**
	 * Returns a reference to the Composite {@link PoolConfigurer} used to apply additional configuration
	 * to this {@link PoolFactoryBean} on Spring container initialization.
	 *
	 * @return the Composite {@link PoolConfigurer}.
	 * @see org.springframework.data.gemfire.config.annotation.PoolConfigurer
	 */
	protected PoolConfigurer getCompositePoolConfigurer() {
		return this.compositePoolConfigurer;
	}

	public void setName(String name) {
		this.name = name;
	}

	protected String getName() {
		return this.name;
	}

	public void setPool(Pool pool) {
		this.pool = pool;
	}

	public Pool getPool() {

		return Optional.ofNullable(this.pool).orElseGet(() -> new PoolAdapter() {

			@Override
			public boolean isDestroyed() {

				Pool pool = PoolFactoryBean.this.pool;

				return pool != null && pool.isDestroyed();
			}

			@Override
			public int getFreeConnectionTimeout() {
				return PoolFactoryBean.this.freeConnectionTimeout;
			}

			@Override
			public long getIdleTimeout() {
				return PoolFactoryBean.this.idleTimeout;
			}

			@Override
			public int getLoadConditioningInterval() {
				return PoolFactoryBean.this.loadConditioningInterval;
			}

			@Override
			public List<InetSocketAddress> getLocators() {
				return PoolFactoryBean.this.locators.toInetSocketAddresses();
			}

			@Override
			public List<InetSocketAddress> getOnlineLocators() {

				return Optional.ofNullable(PoolFactoryBean.this.pool)
					.map(Pool::getOnlineLocators)
					.orElseThrow(() -> newIllegalStateException("Pool [%s] has not been initialized", getName()));
			}

			@Override
			public int getMaxConnections() {
				return PoolFactoryBean.this.maxConnections;
			}

			@Override
			public int getMinConnections() {
				return PoolFactoryBean.this.minConnections;
			}

			@Override
			public boolean getMultiuserAuthentication() {
				return PoolFactoryBean.this.multiUserAuthentication;
			}

			@Override
			public String getName() {

				return Optional.ofNullable(PoolFactoryBean.this.getName())
					.filter(StringUtils::hasText)
					.orElseGet(PoolFactoryBean.this::getBeanName);
			}

			@Override
			public int getPendingEventCount() {

				return Optional.ofNullable(PoolFactoryBean.this.pool)
					.map(Pool::getPendingEventCount)
					.orElseThrow(() -> newIllegalStateException("Pool [%s] has not been initialized", getName()));
			}

			@Override
			public long getPingInterval() {
				return PoolFactoryBean.this.pingInterval;
			}

			@Override
			public boolean getPRSingleHopEnabled() {
				return PoolFactoryBean.this.prSingleHopEnabled;
			}

			@Override
			public QueryService getQueryService() {

				return Optional.ofNullable(PoolFactoryBean.this.pool)
					.map(Pool::getQueryService)
					.orElseThrow(() -> newIllegalStateException("Pool [%s] has not been initialized", getName()));
			}

			@Override
			public int getReadTimeout() {
				return PoolFactoryBean.this.readTimeout;
			}

			@Override
			public int getRetryAttempts() {
				return PoolFactoryBean.this.retryAttempts;
			}

			@Override
			public String getServerGroup() {
				return PoolFactoryBean.this.serverGroup;
			}

			@Override
			public List<InetSocketAddress> getServers() {
				return PoolFactoryBean.this.servers.toInetSocketAddresses();
			}

			@Override
			public int getSocketBufferSize() {
				return PoolFactoryBean.this.socketBufferSize;
			}

			@Override
			public int getSocketConnectTimeout() {
				return PoolFactoryBean.this.socketConnectTimeout;
			}

			@Override
			public int getStatisticInterval() {
				return PoolFactoryBean.this.statisticInterval;
			}

			@Override
			public int getSubscriptionAckInterval() {
				return PoolFactoryBean.this.subscriptionAckInterval;
			}

			@Override
			public boolean getSubscriptionEnabled() {
				return PoolFactoryBean.this.subscriptionEnabled;
			}

			@Override
			public int getSubscriptionMessageTrackingTimeout() {
				return PoolFactoryBean.this.subscriptionMessageTrackingTimeout;
			}

			@Override
			public int getSubscriptionRedundancy() {
				return PoolFactoryBean.this.subscriptionRedundancy;
			}

			@Override
			public int getSubscriptionTimeoutMultiplier() {
				return PoolFactoryBean.this.subscriptionTimeoutMultiplier;
			}

			@Override
			public boolean getThreadLocalConnections() {
				return PoolFactoryBean.this.threadLocalConnections;
			}

			@Override
			public void destroy() {
				destroy(false);
			}

			@Override
			public void destroy(boolean keepAlive) {

				try {
					PoolFactoryBean.this.destroy();
				}
				catch (Exception ignore) {
					Optional.ofNullable(PoolFactoryBean.this.pool).ifPresent(pool -> pool.destroy(keepAlive));
				}
			}

			@Override
			public void releaseThreadLocalConnection() {

				Optional.ofNullable(PoolFactoryBean.this.pool)
					.map(it -> {
						it.releaseThreadLocalConnection();
						return it;
					})
					.orElseThrow(() -> newIllegalStateException("Pool [%s] has not been initialized", getName()));
			}
		});
	}

	public void setFreeConnectionTimeout(int freeConnectionTimeout) {
		this.freeConnectionTimeout = freeConnectionTimeout;
	}

	public void setIdleTimeout(long idleTimeout) {
		this.idleTimeout = idleTimeout;
	}

	public void setKeepAlive(boolean keepAlive) {
		this.keepAlive = keepAlive;
	}

	public void setLoadConditioningInterval(int loadConditioningInterval) {
		this.loadConditioningInterval = loadConditioningInterval;
	}

	public void setLocators(ConnectionEndpoint[] connectionEndpoints) {
		setLocators(ConnectionEndpointList.from(connectionEndpoints));
	}

	public void setLocators(Iterable<ConnectionEndpoint> connectionEndpoints) {
		getLocators().clear();
		getLocators().add(connectionEndpoints);
	}

	ConnectionEndpointList getLocators() {
		return locators;
	}

	public void setMaxConnections(int maxConnections) {
		this.maxConnections = maxConnections;
	}

	public void setMinConnections(int minConnections) {
		this.minConnections = minConnections;
	}

	public void setMultiUserAuthentication(boolean multiUserAuthentication) {
		this.multiUserAuthentication = multiUserAuthentication;
	}

	public void setPingInterval(long pingInterval) {
		this.pingInterval = pingInterval;
	}

	/**
	 * Null-safe operation to set an array of {@link PoolConfigurer PoolConfigurers} used to apply
	 * additional configuration to this {@link PoolFactoryBean} when using Annotation-based configuration.
	 *
	 * @param poolConfigurers array of {@link PoolConfigurer PoolConfigurers} used to apply
	 * additional configuration to this {@link PoolFactoryBean}.
	 * @see org.springframework.data.gemfire.config.annotation.PoolConfigurer
	 * @see #setPoolConfigurers(List)
	 */
	public void setPoolConfigurers(PoolConfigurer... poolConfigurers) {
		setPoolConfigurers(Arrays.asList(nullSafeArray(poolConfigurers, PoolConfigurer.class)));
	}

	/**
	 * Null-safe operation to set an {@link Iterable} of {@link PoolConfigurer PoolConfigurers} used to apply
	 * additional configuration to this {@link PoolFactoryBean} when using Annotation-based configuration.
	 *
	 * @param poolConfigurers {@link Iterable} of {@link PoolConfigurer PoolConfigurers} used to apply
	 * additional configuration to this {@link PoolFactoryBean}.
	 * @see org.springframework.data.gemfire.config.annotation.PoolConfigurer
	 */
	public void setPoolConfigurers(List<PoolConfigurer> poolConfigurers) {
		this.poolConfigurers = Optional.ofNullable(poolConfigurers).orElseGet(Collections::emptyList);
	}

	/**
	 * Sets the {@link PoolFactoryInitializer} to initialize the {@link PoolFactory} used by
	 * this {@link PoolFactoryBean} to create a {@link Pool}.
	 *
	 * @param poolFactoryInitializer {@link PoolFactoryInitializer} user provided callback interface invoked
	 * by this {@link PoolFactoryBean} to initialize the {@link PoolFactory} constructed to create the {@link Pool}.
	 * @see org.springframework.data.gemfire.client.PoolFactoryBean.PoolFactoryInitializer
	 */
	public void setPoolFactoryInitializer(PoolFactoryInitializer poolFactoryInitializer) {
		this.poolFactoryInitializer = poolFactoryInitializer;
	}

	public void setPrSingleHopEnabled(boolean prSingleHopEnabled) {
		this.prSingleHopEnabled = prSingleHopEnabled;
	}

	public void setReadTimeout(int readTimeout) {
		this.readTimeout = readTimeout;
	}

	public void setRetryAttempts(int retryAttempts) {
		this.retryAttempts = retryAttempts;
	}

	public void setServerGroup(String serverGroup) {
		this.serverGroup = serverGroup;
	}

	public void setServers(ConnectionEndpoint[] connectionEndpoints) {
		setServers(ConnectionEndpointList.from(connectionEndpoints));
	}

	public void setServers(Iterable<ConnectionEndpoint> connectionEndpoints) {
		getServers().clear();
		getServers().add(connectionEndpoints);
	}

	ConnectionEndpointList getServers() {
		return servers;
	}

	public void setSocketBufferSize(int socketBufferSize) {
		this.socketBufferSize = socketBufferSize;
	}

	public void setSocketConnectTimeout(int socketConnectTimeout) {
		this.socketConnectTimeout = socketConnectTimeout;
	}

	public void setStatisticInterval(int statisticInterval) {
		this.statisticInterval = statisticInterval;
	}

	public void setSubscriptionAckInterval(int subscriptionAckInterval) {
		this.subscriptionAckInterval = subscriptionAckInterval;
	}

	public void setSubscriptionEnabled(boolean subscriptionEnabled) {
		this.subscriptionEnabled = subscriptionEnabled;
	}

	public void setSubscriptionMessageTrackingTimeout(int subscriptionMessageTrackingTimeout) {
		this.subscriptionMessageTrackingTimeout = subscriptionMessageTrackingTimeout;
	}

	public void setSubscriptionRedundancy(int subscriptionRedundancy) {
		this.subscriptionRedundancy = subscriptionRedundancy;
	}

	public void setSubscriptionTimeoutMultiplier(int subscriptionTimeoutMultiplier) {
		this.subscriptionTimeoutMultiplier = subscriptionTimeoutMultiplier;
	}

	public void setThreadLocalConnections(boolean threadLocalConnections) {
		this.threadLocalConnections = threadLocalConnections;
	}

	// Internal framework use only.
	public final void setLocatorsConfiguration(Object locatorsConfiguration) {
	}

	// Internal framework use only.
	public final void setServersConfiguration(Object serversConfiguration) {
	}

	/**
	 * Callback interface to initialize the {@link PoolFactory} used by this {@link PoolFactoryBean}
	 * to create a {@link Pool} by providing additional or alternative configuration for the factory.
	 *
	 * @see org.apache.geode.cache.client.PoolFactory
	 */
	public interface PoolFactoryInitializer {

		/**
		 * Initializes the given {@link PoolFactory}.
		 *
		 * @param poolFactory {@link PoolFactory} to initialize.
		 * @return the given {@link PoolFactory}.
		 * @see org.apache.geode.cache.client.PoolFactory
		 */
		PoolFactory initialize(PoolFactory poolFactory);

	}
}
