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-2013 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.data.gemfire.listener;

import java.util.LinkedHashSet;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.support.DefaultableDelegatingPoolAdapter;
import org.springframework.data.gemfire.client.support.DelegatingPoolAdapter;
import org.springframework.data.gemfire.config.GemfireConstants;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ErrorHandler;
import org.springframework.util.StringUtils;

import com.gemstone.gemfire.cache.RegionService;
import com.gemstone.gemfire.cache.client.Pool;
import com.gemstone.gemfire.cache.client.PoolManager;
import com.gemstone.gemfire.cache.query.CqAttributes;
import com.gemstone.gemfire.cache.query.CqAttributesFactory;
import com.gemstone.gemfire.cache.query.CqEvent;
import com.gemstone.gemfire.cache.query.CqListener;
import com.gemstone.gemfire.cache.query.CqQuery;
import com.gemstone.gemfire.cache.query.QueryException;
import com.gemstone.gemfire.cache.query.QueryService;

/**
 * Container providing asynchronous behaviour for GemFire continuous queries.
 *
 * @author Costin Leau
 * @author John Blum
 * @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 com.gemstone.gemfire.cache.RegionService
 * @see com.gemstone.gemfire.cache.client.Pool
 * @see com.gemstone.gemfire.cache.client.PoolManager
 * @see com.gemstone.gemfire.cache.query.CqEvent
 * @see com.gemstone.gemfire.cache.query.CqListener
 * @see com.gemstone.gemfire.cache.query.CqQuery
 * @see com.gemstone.gemfire.cache.query.QueryService
 */
@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("%1$s-", ClassUtils.getShortName(
		ContinuousQueryListenerContainer.class));

	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;

	protected final Log logger = LogFactory.getLog(getClass());

	private Queue continuousQueries = new ConcurrentLinkedQueue();

	private QueryService queryService;

	private Set continuousQueryDefinitions = new LinkedHashSet();

	private String beanName;
	private String poolName;

	public void afterPropertiesSet() {
		initQueryService(eagerlyInitializePool(resolvePoolName()));
		initExecutor();
		initContinuousQueries(continuousQueryDefinitions);

		Assert.state(queryService != null, "QueryService was not properly initialized");

		initialized = true;

		if (isAutoStartup()) {
			start();
		}
	}

	/* (non-Javadoc) */
	String resolvePoolName() {
		String poolName = this.poolName;

		if (!StringUtils.hasText(poolName)) {
			String defaultPoolName = GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME;
			poolName = (beanFactory != null && beanFactory.containsBean(defaultPoolName) ? defaultPoolName
				: GemfireUtils.DEFAULT_POOL_NAME);
		}

		return poolName;
	}

	/* (non-Javadoc) */
	String eagerlyInitializePool(String poolName) {
		try {
			if (beanFactory != null && beanFactory.isTypeMatch(poolName, Pool.class)) {
				beanFactory.getBean(poolName, Pool.class);
			}
		}
		catch (BeansException ignore) {
			Assert.notNull(PoolManager.find(poolName), String.format("No GemFire Pool with name [%1$s] was found",
				poolName));
		}

		return poolName;
	}

	/* (non-Javadoc) */
	QueryService initQueryService(String poolName) {
		if (queryService == null || StringUtils.hasText(poolName)) {
			queryService = DefaultableDelegatingPoolAdapter.from(DelegatingPoolAdapter.from(
				PoolManager.find(poolName))).preferPool().getQueryService(queryService);
		}

		return queryService;
	}

	/* (non-Javadoc) */
	Executor initExecutor() {
		if (taskExecutor == null) {
			taskExecutor = createDefaultTaskExecutor();
			manageExecutor = true;
		}

		return taskExecutor;
	}

	/**
	 * Creates a default TaskExecutor. Called if no explicit TaskExecutor has been configured.
	 * 

The default implementation builds a {@link org.springframework.core.task.SimpleAsyncTaskExecutor} * with the specified bean name (or the class name, if no bean name is specified) as thread name prefix.

* * @return an instance of the TaskExecutor used to process CQ events asynchronously. * @see org.springframework.core.task.SimpleAsyncTaskExecutor#SimpleAsyncTaskExecutor(String) */ protected TaskExecutor createDefaultTaskExecutor() { return new SimpleAsyncTaskExecutor(beanName != null ? String.format("%1$s-", beanName) : DEFAULT_THREAD_NAME_PREFIX); } private void initContinuousQueries(Set continuousQueryDefinitions) { // stop the continuous query listener container if currently running... if (isRunning()) { stop(); } // close any existing continuous queries... closeQueries(); // add current continuous queries based on the definitions from the configuration... for (ContinuousQueryDefinition definition : continuousQueryDefinitions) { addContinuousQuery(definition); } } public synchronized void start() { if (!isRunning()) { doStart(); running = true; if (logger.isDebugEnabled()) { logger.debug("Started ContinuousQueryListenerContainer"); } } } private void doStart() { for (CqQuery cq : continuousQueries) { executeQuery(cq); } } private void executeQuery(CqQuery cq) { try { cq.execute(); } catch (QueryException ex) { throw new GemfireQueryException(String.format("Could not execute query [%1$s]; state is [%2$s].", cq.getName(), cq.getState()), ex); } catch (RuntimeException ex) { throw new GemfireQueryException(String.format("Could not execute query [%1$s]; state is [%2$s].", cq.getName(), cq.getState()), ex); } } public synchronized void stop() { if (isRunning()) { doStop(); running = false; } if (logger.isDebugEnabled()) { logger.debug("Stopped ContinuousQueryListenerContainer"); } } public void stop(final Runnable callback) { stop(); callback.run(); } private void doStop() { for (CqQuery cq : continuousQueries) { try { cq.stop(); } catch (Exception e) { logger.warn(String.format("Cannot stop query '%1$s'; state is '%2$s.", cq.getName(), cq.getState()), e); } } } public void destroy() throws Exception { stop(); closeQueries(); destroyExecutor(); initialized = false; } private void closeQueries() { for (CqQuery cq : continuousQueries) { try { if (!cq.isClosed()) { cq.close(); } } catch (Exception e) { logger.warn(String.format("Cannot close query '%1$s'; state is '%2$s.", cq.getName(), cq.getState()), e); } } continuousQueries.clear(); } private void destroyExecutor() throws Exception { if (manageExecutor) { if (taskExecutor instanceof DisposableBean) { ((DisposableBean) taskExecutor).destroy(); if (logger.isDebugEnabled()) { logger.debug("Stopped internally-managed Task Executor."); } } } } /** * Determines whether this container is currently active, that is, whether it has been setup (initialized) * but not shutdown yet. * * @return a boolean indicating whether the container is active. */ public final boolean isActive() { return 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() */ public boolean isAutoStartup() { return 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. */ public synchronized boolean isRunning() { return 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; } /** * 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. */ public void setBeanName(String name) { this.beanName = name; } /** * Set the underlying RegionService (GemFire Cache) used for registering Queries. * * @param cache the RegionService (GemFire Cache) used for registering Queries. * @see com.gemstone.gemfire.cache.RegionService */ public void setCache(RegionService cache) { setQueryService(cache.getQueryService()); } /** * Set an ErrorHandler to be invoked in case of any uncaught exceptions thrown while processing a CQ event. * By default there will be no ErrorHandler so that error-level logging is the only result. * * @param errorHandler the ErrorHandler invoked when uncaught exceptions are thrown while processing the CQ event. * @see org.springframework.util.ErrorHandler */ public void setErrorHandler(ErrorHandler errorHandler) { this.errorHandler = 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() */ public int getPhase() { return 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; } /** * Attaches the given query definitions. * * @param queries set of queries */ public void setQueryListeners(Set queries) { continuousQueryDefinitions.clear(); continuousQueryDefinitions.addAll(queries); } /** * Set the GemFire QueryService used by this container to create ContinuousQueries (CQ). * * @param service the GemFire QueryService object used by the container to create ContinuousQueries (CQ). * @see com.gemstone.gemfire.cache.query.QueryService */ public void setQueryService(QueryService service) { this.queryService = service; } /** * 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; } /** * Adds a 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 Continuous Query (CQ) definition * @see org.springframework.data.gemfire.listener.ContinuousQueryDefinition * @see #doAddListener(ContinuousQueryDefinition) */ public void addListener(ContinuousQueryDefinition definition) { doAddListener(definition); } private void doAddListener(ContinuousQueryDefinition definition) { CqQuery cq = addContinuousQuery(definition); if (isRunning()) { executeQuery(cq); } } private CqQuery addContinuousQuery(ContinuousQueryDefinition definition) { try { CqAttributesFactory continuousQueryAttributesFactory = new CqAttributesFactory(); continuousQueryAttributesFactory.addCqListener(new EventDispatcherAdapter(definition.getListener())); CqAttributes continuousQueryAttributes = continuousQueryAttributesFactory.create(); CqQuery cq = (StringUtils.hasText(definition.getName()) ? queryService.newCq(definition.getName(), definition.getQuery(), continuousQueryAttributes, definition.isDurable()) : queryService.newCq(definition.getQuery(), continuousQueryAttributes, definition.isDurable())); continuousQueries.add(cq); return cq; } catch (RuntimeException ex) { throw new GemfireQueryException("Cannot create query ", ex); } catch (QueryException ex) { throw new GemfireQueryException("Cannot create query ", ex); } } private void dispatchEvent(final ContinuousQueryListener listener, final CqEvent event) { taskExecutor.execute(new Runnable() { public void run() { executeListener(listener, event); } }); } /** * Execute the specified listener. * * @param listener the ContinuousQueryListener to notify of the CQ event. * @param event the CQ event. * @see #handleListenerException(Throwable) */ protected void executeListener(ContinuousQueryListener listener, CqEvent event) { try { listener.onEvent(event); } catch (Throwable ex) { handleListenerException(ex); } } /** * Handle the given exception that arose during listener execution. *

The default implementation logs the exception at error level. * This can be overridden in subclasses. * * @param e the exception to handle */ protected void handleListenerException(Throwable e) { if (isActive()) { // Regular case: failed while active. // Invoke ErrorHandler if available. invokeErrorHandler(e); } else { // Rare case: listener thread failed after container shutdown. // Log at debug level, to avoid spamming the shutdown logger. logger.debug("Listener exception after container shutdown", e); } } /** * Invoke the registered ErrorHandler, if any. Log at error level otherwise. * * @param e the uncaught error that arose during event processing. * @see #setErrorHandler */ protected void invokeErrorHandler(Throwable e) { if (this.errorHandler != null) { this.errorHandler.handleError(e); } else if (logger.isWarnEnabled()) { logger.warn("Execution of the CQ event listener failed, and no ErrorHandler has been set.", e); } } private class EventDispatcherAdapter implements CqListener { private final ContinuousQueryListener delegate; private EventDispatcherAdapter(final ContinuousQueryListener delegate) { this.delegate = delegate; } public void onError(CqEvent event) { dispatchEvent(delegate, event); } public void onEvent(CqEvent event) { dispatchEvent(delegate, event); } public void close() { } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy