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

org.springframework.data.gemfire.listener.adapter.ContinuousQueryListenerAdapter Maven / Gradle / Ivy

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

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.gemfire.listener.ContinuousQueryListener;
import org.springframework.data.gemfire.listener.GemfireListenerExecutionFailedException;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodCallback;
import org.springframework.util.ReflectionUtils.MethodFilter;

import com.gemstone.gemfire.cache.Operation;
import com.gemstone.gemfire.cache.query.CqEvent;
import com.gemstone.gemfire.cache.query.CqQuery;

/**
 * Event listener adapter that delegates the handling of messages to target
 * listener methods via reflection, with flexible event type conversion.
 * Allows listener methods to operate on event content types, completely
 * independent from the GemFire API.
 * 
 * 

Modeled as much as possible after the JMS MessageListenerAdapter in * Spring Framework. * *

By default, the content of incoming GemFire events gets extracted before * being passed into the target listener method, to let the target method * operate on event content types such as Object or Operation instead of * the raw {@link CqEvent}.

* *

Find below some examples of method signatures compliant with this * adapter class. This first example handles all CqEvent types * and gets passed the contents of each event type as an * argument.

* *
public interface PojoListener {
 *    void handleEvent(CqEvent event);
 *    void handleEvent(Operation baseOp);
 *    void handleEvent(Object key);
 *    void handleEvent(Object key, Object newValue);
 *    void handleEvent(Throwable th);
 *    void handleEvent(CqEvent event, Operation baseOp, byte[] deltaValue);
 *    void handleEvent(CqEvent event, Operation baseOp, Operation queryOp, Object key, Object newValue);
 * }
* * @author Juergen Hoeller * @author Costin Leau * @author Oliver Gierke * @author John Blum */ public class ContinuousQueryListenerAdapter implements ContinuousQueryListener { // Out-of-the-box value for the default listener handler method "handleEvent". public static final String DEFAULT_LISTENER_METHOD_NAME = "handleEvent"; protected final Log logger = LogFactory.getLog(getClass()); private MethodInvoker invoker; private Object delegate; private String defaultListenerMethod = DEFAULT_LISTENER_METHOD_NAME; /** * Create a new {@link ContinuousQueryListenerAdapter} with default settings. */ public ContinuousQueryListenerAdapter() { setDelegate(this); } /** * Create a new {@link ContinuousQueryListenerAdapter} for the given delegate. * * @param delegate the delegate object */ public ContinuousQueryListenerAdapter(Object delegate) { setDelegate(delegate); } /** * Set a target object to delegate events listening to. * Specified listener methods have to be present on this target object. *

If no explicit delegate object has been specified, listener * methods are expected to present on this adapter instance, that is, * on a custom subclass of this adapter, defining listener methods. * * @param delegate delegate object */ public void setDelegate(Object delegate) { Assert.notNull(delegate, "'delegate' must not be null"); this.delegate = delegate; this.invoker = null; } /** * Returns the target object to delegate event listening to. * * @return event listening delegation */ public Object getDelegate() { return this.delegate; } /** * Specify the name of the default listener method to delegate to in the case where no specific listener method * has been determined. Out-of-the-box value is {@link #DEFAULT_LISTENER_METHOD_NAME "handleEvent}. * * @param defaultListenerMethod the name of the default listener method to invoke. * @see #getListenerMethodName */ public void setDefaultListenerMethod(String defaultListenerMethod) { this.defaultListenerMethod = defaultListenerMethod; this.invoker = null; } /** * Return the name of the default listener method to delegate to. * * @return the name of the default listener method to invoke on CQ events. */ protected String getDefaultListenerMethod() { return this.defaultListenerMethod; } /** * Standard {@link ContinuousQueryListener} entry point. *

Delegates the event to the target listener method, with appropriate * conversion of the event argument. In case of an exception, the * {@link #handleListenerException(Throwable)} method will be invoked. * * @param event the incoming GemFire event * @see #handleListenerException */ public void onEvent(CqEvent event) { try { // Check whether the delegate is a ContinuousQueryListener implementation itself. // If so, this adapter will simply act as a pass-through. if (delegate != this && delegate instanceof ContinuousQueryListener) { ((ContinuousQueryListener) delegate).onEvent(event); } // Else... find the listener handler method reflectively. else { String methodName = getListenerMethodName(event); if (methodName == null) { throw new InvalidDataAccessApiUsageException("No default listener method specified." + " Either specify a non-null value for the 'defaultListenerMethod' property" + " or override the 'getListenerMethodName' method."); } invoker = (invoker != null ? invoker : new MethodInvoker(delegate, methodName)); invokeListenerMethod(event, methodName); } } catch (Throwable cause) { handleListenerException(cause); } } /** * Determine the name of the listener method that is supposed to * handle the given event. *

The default implementation simply returns the configured * default listener method, if any. * @param event the GemFire event * @return the name of the listener method (never null) * @see #setDefaultListenerMethod */ @SuppressWarnings("unused") protected String getListenerMethodName(CqEvent event) { return getDefaultListenerMethod(); } /** * Handle the given exception that arose during listener execution. * The default implementation logs the exception at error level. * @param cause the exception to handle */ protected void handleListenerException(Throwable cause) { logger.error("Listener execution failed...", cause); } /** * Invoke the specified listener method. * @param event the event arguments to be passed in * @param methodName the method to invoke * @see #getListenerMethodName */ protected void invokeListenerMethod(CqEvent event, String methodName) { try { invoker.invoke(event); } catch (InvocationTargetException e) { if (e.getTargetException() instanceof DataAccessException) { throw (DataAccessException) e.getTargetException(); } else { throw new GemfireListenerExecutionFailedException( String.format("Listener method [%1$s] threw Exception...", methodName), e.getTargetException()); } } catch (Throwable e) { throw new GemfireListenerExecutionFailedException( String.format("Failed to invoke the target listener method [%1$s]", methodName), e); } } private class MethodInvoker { private final Object delegate; List methods; MethodInvoker(Object delegate, final String methodName) { Class c = delegate.getClass(); this.delegate = delegate; methods = new ArrayList(); ReflectionUtils.doWithMethods(c, new MethodCallback() { public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { ReflectionUtils.makeAccessible(method); methods.add(method); } }, new MethodFilter() { public boolean matches(Method method) { return isValidEventMethodSignature(method, methodName); } }); Assert.isTrue(!methods.isEmpty(), String.format( "Cannot find a suitable method named [%1$s#%2$s] - is the method public and does it have the proper arguments?", c.getName(), methodName)); } @SuppressWarnings("all") boolean isValidEventMethodSignature(Method method, String methodName) { if (Modifier.isPublic(method.getModifiers()) && methodName.equals(method.getName())) { Class[] parameterTypes = method.getParameterTypes(); int objects = 0; int operations = 0; if (parameterTypes.length > 0) { for (Class parameterType : parameterTypes) { if (Object.class.equals(parameterType)) { if (++objects > 2) { return false; } } else if (Operation.class.equals(parameterType)) { if (++operations > 2) { return false; } } else if (byte[].class.equals(parameterType)) { } else if (CqEvent.class.equals(parameterType)) { } else if (CqQuery.class.equals(parameterType)) { } else if (Throwable.class.equals(parameterType)) { } else { return false; } } return true; } } return false; } void invoke(CqEvent event) throws InvocationTargetException, IllegalAccessException { for (Method method : methods) { method.invoke(delegate, getMethodArguments(method, event)); } } Object[] getMethodArguments(Method method, CqEvent event) { Class[] parameterTypes = method.getParameterTypes(); Object[] args = new Object[parameterTypes.length]; boolean query = false; boolean value = false; for (int index = 0; index < parameterTypes.length; index++) { Class parameterType = parameterTypes[index]; if (Object.class.equals(parameterType)) { args[index] = (value ? event.getNewValue() : event.getKey()); value = true; } else if (Operation.class.equals(parameterType)) { args[index] = (query ? event.getQueryOperation() : event.getBaseOperation()); query = true; } else if (byte[].class.equals(parameterType)) { args[index] = event.getDeltaValue(); } else if (CqEvent.class.equals(parameterType)) { args[index] = event; } else if (CqQuery.class.equals(parameterType)) { args[index] = event.getCq(); } else if (Throwable.class.equals(parameterType)) { args[index] = event.getThrowable(); } } return args; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy