All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.data.gemfire.listener.ContinuousQueryListenerContainer Maven / Gradle / Ivy

There is a newer version: 2.3.9.RELEASE
Show newest version
/*
 * Copyright 2011-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.listener;

import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeList;
import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeSet;

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
import java.util.function.Supplier;
import java.util.stream.StreamSupport;

import org.apache.geode.cache.RegionService;
import org.apache.geode.cache.client.Pool;
import org.apache.geode.cache.query.CqAttributes;
import org.apache.geode.cache.query.CqEvent;
import org.apache.geode.cache.query.CqException;
import org.apache.geode.cache.query.CqListener;
import org.apache.geode.cache.query.CqQuery;
import org.apache.geode.cache.query.QueryException;
import org.apache.geode.cache.query.QueryService;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.beans.BeansException;
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.InitializingBean;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.data.gemfire.GemfireQueryException;
import org.springframework.data.gemfire.GemfireUtils;
import org.springframework.data.gemfire.client.PoolResolver;
import org.springframework.data.gemfire.client.support.DefaultableDelegatingPoolAdapter;
import org.springframework.data.gemfire.client.support.DelegatingPoolAdapter;
import org.springframework.data.gemfire.client.support.PoolManagerPoolResolver;
import org.springframework.data.gemfire.config.annotation.ContinuousQueryListenerContainerConfigurer;
import org.springframework.data.gemfire.config.xml.GemfireConstants;
import org.springframework.data.gemfire.util.ArrayUtils;
import org.springframework.data.gemfire.util.CollectionUtils;
import org.springframework.data.gemfire.util.SpringUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ErrorHandler;
import org.springframework.util.StringUtils;

/**
 * Container providing asynchronous processing/handling for Apache Geode Continuous Queries (CQ).
 *
 * @author Costin Leau
 * @author John Blum
 * @see java.util.concurrent.Executor
 * @see org.apache.geode.cache.RegionService
 * @see org.apache.geode.cache.client.Pool
 * @see org.apache.geode.cache.client.PoolManager
 * @see org.apache.geode.cache.query.CqAttributes
 * @see org.apache.geode.cache.query.CqEvent
 * @see org.apache.geode.cache.query.CqListener
 * @see org.apache.geode.cache.query.CqQuery
 * @see org.apache.geode.cache.query.QueryService
 * @see org.springframework.beans.factory.BeanFactory
 * @see org.springframework.beans.factory.BeanFactoryAware
 * @see org.springframework.beans.factory.BeanNameAware
 * @see org.springframework.beans.factory.DisposableBean
 * @see org.springframework.beans.factory.InitializingBean
 * @see org.springframework.context.SmartLifecycle
 * @see org.springframework.core.task.SimpleAsyncTaskExecutor
 * @see org.springframework.core.task.TaskExecutor
 * @see org.springframework.data.gemfire.client.support.DefaultableDelegatingPoolAdapter
 * @see org.springframework.data.gemfire.client.support.DelegatingPoolAdapter
 * @see org.springframework.util.ErrorHandler
 * @since 1.1.0
 */
@SuppressWarnings("unused")
public class ContinuousQueryListenerContainer implements BeanFactoryAware, BeanNameAware,
		InitializingBean, DisposableBean, SmartLifecycle {

	// Default Thread name prefix is "ContinuousQueryListenerContainer-"
	public static final String DEFAULT_THREAD_NAME_PREFIX =
		String.format("%s-", ContinuousQueryListenerContainer.class.getSimpleName());

	// Default PoolResolver uses Apache Geode's PoolManager
	protected static final PoolResolver DEFAULT_POOL_RESOLVER = new PoolManagerPoolResolver();

	private boolean autoStartup = true;

	private volatile boolean initialized = false;
	private volatile boolean manageExecutor = false;
	private volatile boolean running = false;

	private int phase = Integer.MAX_VALUE;

	private BeanFactory beanFactory;

	private ErrorHandler errorHandler;

	private Executor taskExecutor;

	private List cqListenerContainerConfigurers = Collections.emptyList();

	private ContinuousQueryListenerContainerConfigurer compositeCqListenerContainerConfigurer =
		(beanName, container) -> nullSafeList(this.cqListenerContainerConfigurers).forEach(configurer ->
			configurer.configure(beanName, container));

	protected final Logger logger = LoggerFactory.getLogger(getClass());

	private PoolResolver poolResolver = DEFAULT_POOL_RESOLVER;

	private Queue continuousQueries = new ConcurrentLinkedQueue<>();

	private QueryService queryService;

	private Set continuousQueryDefinitions = new LinkedHashSet<>();

	private String beanName;
	private String poolName;

	@Override
	public void afterPropertiesSet() {

		applyContinuousQueryListenerContainerConfigurers();
		validateQueryService(initQueryService(eagerlyInitializePool(resolvePoolName())));
		initExecutor();
		initContinuousQueries();

		this.initialized = true;
	}

	/**
	 * Applies configuration customizations to this {@link ContinuousQueryListenerContainer} from the registered
	 * composite {@link ContinuousQueryListenerContainerConfigurer} objects.
	 *
	 * @see #getCompositeContinuousQueryListenerContainerConfigurer()
	 * @see #applyContinuousQueryListenerContainerConfigurers(ContinuousQueryListenerContainerConfigurer...)
	 */
	private void applyContinuousQueryListenerContainerConfigurers() {
		applyContinuousQueryListenerContainerConfigurers(getCompositeContinuousQueryListenerContainerConfigurer());
	}

	/**
	 * Applies an array of {@link ContinuousQueryListenerContainerConfigurer} objects to customize the configuration
	 * of this {@link ContinuousQueryListenerContainer}.
	 *
	 * @param configurers array of {@link ContinuousQueryListenerContainerConfigurer} used to customize
	 * the configuration of this {@link ContinuousQueryListenerContainer}.
	 * @see org.springframework.data.gemfire.config.annotation.ContinuousQueryListenerContainerConfigurer
	 */
	protected void applyContinuousQueryListenerContainerConfigurers(
			ContinuousQueryListenerContainerConfigurer... configurers) {

		List configurerList =
			Arrays.asList(ArrayUtils.nullSafeArray(configurers, ContinuousQueryListenerContainerConfigurer.class));

		applyContinuousQueryListenerContainerConfigurers(configurerList);
	}

	/**
	 * Applies an {@link Iterable} of {@link ContinuousQueryListenerContainerConfigurer} objects to customize
	 * the configuration of this {@link ContinuousQueryListenerContainer}.
	 *
	 * @param configurers {@link Iterable} of {@link ContinuousQueryListenerContainerConfigurer} used to customize
	 * the configuration of this {@link ContinuousQueryListenerContainer}.
	 * @see org.springframework.data.gemfire.config.annotation.ContinuousQueryListenerContainerConfigurer
	 */
	protected void applyContinuousQueryListenerContainerConfigurers(
			Iterable configurers) {

		StreamSupport.stream(CollectionUtils.nullSafeIterable(configurers).spliterator(), false)
			.forEach(configurer -> configurer.configure(getBeanName(), this));
	}

	/**
	 * Resolves a {@link Pool} object with the given {@link String name} from the configured {@link PoolResolver}.
	 *
	 * @param poolName {@link String name} of the {@link Pool} to resolve.
	 * @return a resolved {@link Pool} object from the given {@link String name}.
	 * @see org.apache.geode.cache.client.Pool
	 * @see #getPoolResolver()
	 */
	@Nullable Pool resolvePool(String poolName) {
		return getPoolResolver().resolve(poolName);
	}

	/**
	 * Resolves the name of the {@link Pool} configured to handle the registered Continuous Queries.
	 *
	 * Note, the {@link Pool} must have subscription enabled.
	 *
	 * @return the {@link String name} of the {@link Pool} configured to handle the registered Continuous Queries.
	 */
	String resolvePoolName() {

		return Optional.ofNullable(getPoolName())
			.filter(StringUtils::hasText)
			.orElseGet(() ->
				Optional.ofNullable(getBeanFactory())
					.filter(it -> SpringUtils.isMatchingBean(it, GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME, Pool.class))
					.map(it -> GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME)
					.orElse(GemfireUtils.DEFAULT_POOL_NAME));
	}

	/**
	 * Eagerly initializes the {@link Pool} with the given {@link String name}.
	 *
	 * First, this method attempts to use the configured {@link BeanFactory}, if not {@literal null}, to find a bean
	 * in the Spring container of type {@link Pool} having the given {@link String name }and fetch the bean,
	 * thereby causing the {@link Pool} bean to be initialized.
	 *
	 * However, if the {@link BeanFactory} was not configured, or no bean exists in the Spring container
	 * with the given {@link String name} or of the {@link Pool} type, then the named {@link Pool} is looked up
	 * in GemFire/Geode's {@link org.apache.geode.cache.client.PoolManager}.
	 *
	 * @param poolName {@link String} containing the name of the {@link Pool} to initialize.
	 * @return the given {@link Pool} name.
	 */
	String eagerlyInitializePool(String poolName) {

		Supplier poolNameResolver = () -> {
			Assert.notNull(resolvePool(poolName), String.format("No Pool with name [%s] was found", poolName));
			return poolName;
		};

		return Optional.ofNullable(getBeanFactory())
			.filter(it -> SpringUtils.isMatchingBean(it, poolName, Pool.class))
			.map(it -> {
				try {
					it.getBean(poolName, Pool.class);
					return poolName;
				}
				catch (BeansException ignore) {
					return poolNameResolver.get();
				}
			})
			.orElseGet(poolNameResolver);
	}

	/**
	 * Initializes the {@link QueryService} used to register Continuous Queries (CQ).
	 *
	 * @param poolName {@link String} containing the name of the {@link Pool} used obtain the {@link QueryService}
	 * if CQs are tied to a specific {@link Pool}.
	 * @return the initialized {@link QueryService}.
	 * @see org.apache.geode.cache.query.QueryService
	 */
	QueryService initQueryService(String poolName) {

		QueryService queryService = getQueryService();

		if (queryService == null || StringUtils.hasText(poolName)) {

			Pool resolvedPool = resolvePool(poolName);

			DefaultableDelegatingPoolAdapter poolAdapter =
				DefaultableDelegatingPoolAdapter.from(DelegatingPoolAdapter.from(resolvedPool));

			setQueryService(poolAdapter.preferPool().getQueryService(queryService));
		}

		return getQueryService();
	}

	/**
	 * Verifies the given {@link QueryService} is valid.
	 *
	 * @param queryService {@link QueryService} to validate.
	 * @throws IllegalStateException if the {@link QueryService} is {@literal null}.
	 * @return the given {@link QueryService}
	 * @see org.apache.geode.cache.query.QueryService
	 */
	private QueryService validateQueryService(QueryService queryService) {

		Assert.state(queryService != null, "QueryService is required");

		return queryService;
	}

	/**
	 * Initialize the {@link Executor} used to process CQ events asynchronously.
	 *
	 * @return a new isntance of {@link Executor} used to process CQ events asynchronously.
	 * @see java.util.concurrent.Executor
	 */
	Executor initExecutor() {

		if (getTaskExecutor() == null) {
			setTaskExecutor(createDefaultTaskExecutor());
			this.manageExecutor = true;
		}

		return getTaskExecutor();
	}

	/**
	 * Creates a default {@link TaskExecutor}.
	 *
	 * Called if no explicit {@link TaskExecutor} has been configured.
	 *
	 * The default implementation builds a {@link SimpleAsyncTaskExecutor} with the specified bean name
	 * (or the class name, if no bean name is specified) as the Thread name prefix.
	 *
	 * @return an instance of the {@link TaskExecutor} used to process CQ events asynchronously.
	 * @see org.springframework.core.task.SimpleAsyncTaskExecutor
	 */
	protected Executor createDefaultTaskExecutor() {

		String threadNamePrefix = Optional.ofNullable(getBeanName())
			.filter(StringUtils::hasText)
			.map(it -> String.format("%s-", it))
			.orElse(DEFAULT_THREAD_NAME_PREFIX);

		return new SimpleAsyncTaskExecutor(threadNamePrefix);
	}

	/**
	 * Initializes all the {@link CqQuery Continuous Queries} defined by
	 * the {@link ContinuousQueryDefinition Continuous Query Defintions}.
	 *
	 * @see #getContinuousQueryDefinitions()
	 * @see #initContinuousQueries(Set)
	 */
	private void initContinuousQueries() {
		initContinuousQueries(getContinuousQueryDefinitions());
	}

	private void initContinuousQueries(Set continuousQueryDefinitions) {

		// Stop the ContinuousQueryListenerContainer if currently running...
		stop();

		// Close any existing continuous queries...
		closeQueries();

		// Add current continuous queries based on the definitions from the configuration...
		continuousQueryDefinitions.forEach(this::addContinuousQuery);
	}

	/**
	 * Determines whether this container is currently active, i.e., whether it has been setup and initialized
	 * but not shutdown yet.
	 *
	 * @return a boolean indicating whether the container is active.
	 */
	public boolean isActive() {
		return this.initialized;
	}

	/**
	 * Sets whether the CQ listener container should automatically start on startup.
	 *
	 * @param autoStartup a boolean value indicating whether this CQ listener container should automatically start.
	 */
	public void setAutoStartup(final boolean autoStartup) {
		this.autoStartup = autoStartup;
	}

	/**
	 * Determines whether this CQ listener container will automatically start on startup.
	 *
	 * @return a boolean value indicating whether this CQ listener container automatically starts.
	 * @see org.springframework.context.SmartLifecycle#isAutoStartup()
	 */
	@Override
	public boolean isAutoStartup() {
		return this.autoStartup;
	}

	/**
	 * Determines whether the container has be started and is currently running.
	 *
	 * @return a boolean value indicating whether the container has been started and is currently running.
	 */
	@Override
	public synchronized boolean isRunning() {
		return this.running;
	}

	/**
	 * Sets the {@link BeanFactory} containing this bean.
	 *
	 * @param beanFactory the Spring {@link BeanFactory} containing this bean.
	 * @throws BeansException if an initialization error occurs.
	 */
	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		this.beanFactory = beanFactory;
	}

	/**
	 * Returns a reference to the configured {@link BeanFactory}.
	 *
	 * @return a reference to the configured {@link BeanFactory}.
	 * @see org.springframework.beans.factory.BeanFactory
	 */
	protected BeanFactory getBeanFactory() {
		return this.beanFactory;
	}

	/**
	 * Set the name of the bean in the bean factory that created this bean.
	 * 

Invoked after population of normal bean properties but before an * init callback such as {@link InitializingBean#afterPropertiesSet()} * or a custom init-method.

* * @param name the name of the bean in the factory. */ @Override public void setBeanName(String name) { this.beanName = name; } /** * Returns the configured {@link String bean name} of this container. * * @return the configured {@link String bean name} of this container. */ protected String getBeanName() { return this.beanName; } /** * Set the underlying RegionService (Pivotal GemFire Cache) used for registering Queries. * * @param cache the RegionService (Pivotal GemFire Cache) used for registering Queries. * @see org.apache.geode.cache.RegionService */ public void setCache(RegionService cache) { setQueryService(cache.getQueryService()); } /** * Returns a reference to all the configured/registered {@link CqQuery Continuous Queries}. * * @return a reference to all the configured/registered {@link CqQuery Continuous Queries}. * @see org.apache.geode.cache.query.CqQuery * @see java.util.Queue */ protected Queue getContinuousQueries() { return this.continuousQueries; } /** * Returns a reference to all the configured {@link ContinuousQueryDefinition ContinuousQueryDefinitions}. * * @return a reference to all the configured {@link ContinuousQueryDefinition ContinuousQueryDefinitions}. * @see org.springframework.data.gemfire.listener.ContinuousQueryDefinition * @see java.util.Set */ protected Set getContinuousQueryDefinitions() { return this.continuousQueryDefinitions; } /** * Null-safe operation setting an array of {@link ContinuousQueryListenerContainerConfigurer} objects used to * customize the configuration of this {@link ContinuousQueryListenerContainer}. * * @param configurers array of {@link ContinuousQueryListenerContainerConfigurer} objects used to customize * the configuration of this {@link ContinuousQueryListenerContainer}. * @see org.springframework.data.gemfire.config.annotation.ContinuousQueryListenerContainerConfigurer * @see #setContinuousQueryListenerContainerConfigurers(List) */ public void setContinuousQueryListenerContainerConfigurers(ContinuousQueryListenerContainerConfigurer... configurers) { List configurerList = Arrays.asList(ArrayUtils.nullSafeArray(configurers, ContinuousQueryListenerContainerConfigurer.class)); setContinuousQueryListenerContainerConfigurers(configurerList); } /** * Null-safe operation setting an {@link Iterable} of {@link ContinuousQueryListenerContainerConfigurer} objects * used to customize the configuration of this {@link ContinuousQueryListenerContainer}. * * @param configurers {@link Iterable} of {@link ContinuousQueryListenerContainerConfigurer} objects used to * customize the configuration of this {@link ContinuousQueryListenerContainer}. * @see org.springframework.data.gemfire.config.annotation.ContinuousQueryListenerContainerConfigurer */ public void setContinuousQueryListenerContainerConfigurers(List configurers) { this.cqListenerContainerConfigurers = CollectionUtils.nullSafeList(configurers); } /** * Returns a Composite object containing * the collection of {@link ContinuousQueryListenerContainerConfigurer} objects used to customize the configuration * of this {@link ContinuousQueryListenerContainer}. * * @return a Composite object containing a collection of {@link ContinuousQueryListenerContainerConfigurer} objects * used to customize the configuration of this {@link ContinuousQueryListenerContainer}. * @see org.springframework.data.gemfire.config.annotation.ContinuousQueryListenerContainerConfigurer */ protected ContinuousQueryListenerContainerConfigurer getCompositeContinuousQueryListenerContainerConfigurer() { return this.compositeCqListenerContainerConfigurer; } /** * Set an {@link ErrorHandler} to be invoked in case of any uncaught {@link Exception Exceptions} thrown * while processing a CQ event. * * By default there is no {@link ErrorHandler} configured so error-level logging is the only result. * * @param errorHandler {@link ErrorHandler} invoked when uncaught {@link Exception Exceptions} are thrown * while processing the CQ event. * @see org.springframework.util.ErrorHandler */ public void setErrorHandler(ErrorHandler errorHandler) { this.errorHandler = errorHandler; } /** * Returns an {@link Optional} reference to the configured {@link ErrorHandler} invoked when * any unhandled {@link Exception Exceptions} are thrown when invoking CQ listeners processing CQ events. * * @return an {@link Optional} reference to the configured {@link ErrorHandler}. * @see org.springframework.util.ErrorHandler */ public Optional getErrorHandler() { return Optional.ofNullable(this.errorHandler); } /** * Sets the phase in which this CQ listener container will start in the Spring container. * * @param phase the phase value of this CQ listener container. */ public void setPhase(final int phase) { this.phase = phase; } /** * Gets the phase in which this CQ listener container will start in the Spring container. * * @return the phase value of this CQ listener container. * @see org.springframework.context.Phased#getPhase() */ @Override public int getPhase() { return this.phase; } /** * Set the name of the {@link Pool} used for performing the queries by this container. * * @param poolName the name of the pool to be used by the container */ public void setPoolName(String poolName) { this.poolName = poolName; } /** * Returns the configured {@link String pool name}. * * @return the configured {@link String pool name}. */ public String getPoolName() { return this.poolName; } /** * Configures the {@link PoolResolver} to resolve {@link Pool} objects by {@link String name} * from the Apache Geode cache. * * @param poolResolver the configured {@link PoolResolver} used to resolve {@link Pool} objects * by {@link String name}. * @see org.springframework.data.gemfire.client.PoolResolver */ public void setPoolResolver(PoolResolver poolResolver) { this.poolResolver = poolResolver; } /** * Returns the configured {@link PoolResolver} used to resolve {@link Pool} object by {@link String name}. * * @return the configured {@link PoolResolver}. * @see org.springframework.data.gemfire.client.PoolResolver */ public PoolResolver getPoolResolver() { return this.poolResolver != null ? this.poolResolver : DEFAULT_POOL_RESOLVER; } /** * Attaches the given query definitions. * * @param queries set of queries */ public void setQueryListeners(Set queries) { getContinuousQueryDefinitions().clear(); getContinuousQueryDefinitions().addAll(nullSafeSet(queries)); } /** * Set the Pivotal GemFire QueryService used by this container to create ContinuousQueries (CQ). * * @param queryService the Pivotal GemFire QueryService object used by the container to create ContinuousQueries (CQ). * @see org.apache.geode.cache.query.QueryService */ public void setQueryService(QueryService queryService) { this.queryService = queryService; } /** * Returns a reference to the configured {@link QueryService}. * * @return a reference to the configured {@link QueryService}. * @see org.apache.geode.cache.query.QueryService */ public QueryService getQueryService() { return this.queryService; } /** * Sets the Task Executor used for running the event listeners when messages are received. * If no task executor is set, an instance of {@link SimpleAsyncTaskExecutor} will be used by default. * The task executor can be adjusted depending on the work done by the listeners and the number of * messages coming in. * * @param taskExecutor The Task Executor used to run event listeners when query results messages are received. * @see java.util.concurrent.Executor */ public void setTaskExecutor(Executor taskExecutor) { this.taskExecutor = taskExecutor; } /** * Returns a reference to the configured {@link Executor TaskExecutor}. * * @return a reference to the configured {@link Executor TaskExecutor}. * @see java.util.concurrent.Executor */ public Executor getTaskExecutor() { return this.taskExecutor; } /** * Adds a {@link ContinuousQueryDefinition Continuous Query (CQ) definition} to the (potentially running) container. * * If the container is running, the listener starts receiving (matching) messages as soon as possible. * * @param definition {@link ContinuousQueryDefinition Continuous Query (CQ) definition} to register. * @see org.springframework.data.gemfire.listener.ContinuousQueryDefinition */ public void addListener(ContinuousQueryDefinition definition) { CqQuery query = addContinuousQuery(definition); if (isRunning()) { execute(query); } } public boolean addContinuousQueryDefinition(ContinuousQueryDefinition definition) { return Optional.ofNullable(definition) .map(getContinuousQueryDefinitions()::add) .orElse(false); } CqQuery addContinuousQuery(ContinuousQueryDefinition definition) { try { CqAttributes attributes = definition.toCqAttributes(this::newCqListener); CqQuery query = definition.isNamed() ? newNamedContinuousQuery(definition, attributes) : newUnnamedContinuousQuery(definition, attributes); return manage(query); } catch (QueryException cause) { throw new GemfireQueryException(String.format("Unable to create query [%s]", definition.getQuery()), cause); } } protected CqListener newCqListener(ContinuousQueryListener listener) { return new EventDispatcherAdapter(listener); } private CqQuery newNamedContinuousQuery(ContinuousQueryDefinition definition, CqAttributes attributes) throws QueryException { return getQueryService().newCq(definition.getName(), definition.getQuery(), attributes, definition.isDurable()); } private CqQuery newUnnamedContinuousQuery(ContinuousQueryDefinition definition, CqAttributes attributes) throws CqException { return getQueryService().newCq(definition.getQuery(), attributes, definition.isDurable()); } private CqQuery manage(CqQuery query) { getContinuousQueries().add(query); return query; } @Override public synchronized void start() { if (!isRunning()) { doStart(); this.running = true; if (this.logger.isDebugEnabled()) { this.logger.debug("Started ContinuousQueryListenerContainer"); } } } void doStart() { getContinuousQueries().forEach(this::execute); } private void execute(CqQuery query) { try { query.execute(); } catch (QueryException cause) { throw new GemfireQueryException(String.format("Could not execute query [%1$s]; state is [%2$s]", query.getName(), query.getState()), cause); } } /** * Asynchronously dispatches the {@link CqEvent CQ event} to the targeted {@link ContinuousQueryListener}. * * @param listener {@link ContinuousQueryListener} which will process/handle the {@link CqEvent CQ event}. * @param event {@link CqEvent CQ event} to process. * @see org.springframework.data.gemfire.listener.ContinuousQueryListener * @see org.apache.geode.cache.query.CqEvent */ protected void dispatchEvent(ContinuousQueryListener listener, CqEvent event) { getTaskExecutor().execute(() -> notify(listener, event)); } /** * Invoke the specified {@link ContinuousQueryListener listener} to process/handle the {@link CqEvent CQ event}. * * @param listener {@link ContinuousQueryListener} to notify of the {@link CqEvent CQ event}. * @param event {@link CqEvent CQ event} to process/handle. * @see #handleListenerError(Throwable) */ private void notify(ContinuousQueryListener listener, CqEvent event) { try { listener.onEvent(event); } catch (Throwable cause) { handleListenerError(cause); } } /** * Invokes the configured {@link ErrorHandler} (if any) to handle the {@link Exception} thrown by the CQ listener. * * Logs at warning level if no {@link ErrorHandler} was configured. * * Logs at debug level if the CQ listener container was shutdown at the time when the CQ listener * {@link Exception} was thrown. * * @param cause {@link Throwable uncaught error} thrown during normal CQ event processing. * @see #setErrorHandler(ErrorHandler) * @see #getErrorHandler() */ private void handleListenerError(Throwable cause) { getErrorHandler().filter(errorHandler -> { boolean active = this.isActive(); if (!active && this.logger.isDebugEnabled()) { this.logger.debug("A CQ listener exception occurred after container shutdown; ErrorHandler will not be invoked", cause); } return active; }) .ifPresent(errorHandler -> errorHandler.handleError(cause)); if (!getErrorHandler().isPresent() && this.logger.isWarnEnabled()) { this.logger.warn("Execution of CQ listener failed; No ErrorHandler was configured", cause); } } @Override public void stop(Runnable callback) { stop(); callback.run(); } @Override public synchronized void stop() { if (isRunning()) { doStop(); this.running = false; } if (this.logger.isDebugEnabled()) { this.logger.debug("Stopped ContinuousQueryListenerContainer"); } } void doStop() { getContinuousQueries().forEach(query -> { try { query.stop(); } catch (Exception cause) { if (this.logger.isWarnEnabled()) { this.logger.warn(String.format("Cannot stop query [%1$s]; state is [%2$s]", query.getName(), query.getState()), cause); } } }); } @Override public void destroy() { stop(); closeQueries(); destroyExecutor(); this.initialized = false; } private void closeQueries() { getContinuousQueries().stream() .filter(query -> !query.isClosed()) .forEach(query -> { try { query.close(); } catch (Exception cause) { if (logger.isWarnEnabled()) { logger.warn(String.format("Cannot close query [%1$s]; state is [%2$s]", query.getName(), query.getState()), cause); } } }); getContinuousQueries().clear(); } private void destroyExecutor() { Optional.ofNullable(getTaskExecutor()) .filter(it -> this.manageExecutor) .filter(DisposableBean.class::isInstance) .ifPresent(it -> { try { ((DisposableBean) it).destroy(); if (this.logger.isDebugEnabled()) { this.logger.debug("Stopped internally-managed TaskExecutor {}", it); } } catch (Exception cause) { this.logger.warn("Failed to properly destroy the managed TaskExecutor {}: {}", it, cause.getMessage()); } }); } protected class EventDispatcherAdapter implements CqListener { private final ContinuousQueryListener listener; protected EventDispatcherAdapter(ContinuousQueryListener listener) { Assert.notNull(listener, "ContinuousQueryListener is required"); this.listener = listener; } protected ContinuousQueryListener getListener() { return this.listener; } public void onError(CqEvent event) { dispatchEvent(getListener(), event); } public void onEvent(CqEvent event) { dispatchEvent(getListener(), event); } public void close() { } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy