SGF-358 - Enhance SDG's Function annotation support to allow strongly-typed arguments in the context of PDX even when read-serialized is set to true.

This commit is contained in:
John Blum
2014-12-12 16:40:51 -08:00
parent 20a8257574
commit 743dd48127
12 changed files with 1515 additions and 169 deletions

View File

@@ -12,6 +12,8 @@
*/
package org.springframework.data.gemfire.function;
import java.lang.reflect.Method;
import com.gemstone.gemfire.cache.execute.FunctionContext;
/**
@@ -23,7 +25,19 @@ class DefaultFunctionArgumentResolver implements FunctionArgumentResolver {
private static final Object[] EMPTY_ARRAY = new Object[0];
/* (non-Javadoc)
/*
* (non-Javadoc)
*
* @see java.lang.reflect.Method
*/
@Override
public Method getFunctionAnnotatedMethod() {
throw new UnsupportedOperationException("Not Implemented!");
}
/*
* (non-Javadoc)
*
* @see org.springframework.data.gemfire.function.FunctionArgumentResolver#resolveFunctionArguments(com.gemstone.gemfire.cache.execute.FunctionContext)
*/
@Override

View File

@@ -12,14 +12,23 @@
*/
package org.springframework.data.gemfire.function;
import java.lang.reflect.Method;
import com.gemstone.gemfire.cache.execute.FunctionContext;
/**
* Strategy Interface for resolving function invocation arguments, given a {@link FunctionContext}
* @author David Turanski
* @since 1.3.0
* The FunctionArgumentResolver interface is a Strategy Interface for resolving Function invocation arguments,
* given a {@link FunctionContext}.
*
* @author David Turanski
* @author John Blum
* @see com.gemstone.gemfire.cache.execute.FunctionContext
* @since 1.3.0
*/
interface FunctionArgumentResolver {
Method getFunctionAnnotatedMethod();
Object[] resolveFunctionArguments(FunctionContext functionContext);
}

View File

@@ -34,68 +34,68 @@ import com.gemstone.gemfire.cache.partition.PartitionRegionHelper;
* @since 1.3.0
*
*/
class FunctionContextInjectingArgumentResolver extends DefaultFunctionArgumentResolver {
class FunctionContextInjectingArgumentResolver extends PdxFunctionArgumentResolver {
private static Log logger = LogFactory.getLog(FunctionContextInjectingArgumentResolver.class);
private static final Log logger = LogFactory.getLog(FunctionContextInjectingArgumentResolver.class);
private final int regionParameterPosition;
private final int filterParameterPosition;
private final int functionContextParameterPosition;
private final int regionParameterPosition;
private final int resultSenderParameterPosition;
private final Method method;
public FunctionContextInjectingArgumentResolver(Method method) {
this.method = method;
int annotatedRegionDataParameterPosition = GemfireFunctionUtils.getAnnotationParameterPosition(method,
RegionData.class, new Class[] { Map.class });
int regionDataAnnotationParameterPosition = GemfireFunctionUtils.getAnnotationParameterPosition(
method, RegionData.class, new Class[] { Map.class });
int regionTypeParameterPosition = getArgumentTypePosition(method, Region.class);
if (annotatedRegionDataParameterPosition >= 0 && regionTypeParameterPosition >= 0) {
Assert.isTrue(
annotatedRegionDataParameterPosition == regionTypeParameterPosition,
String.format(
"Function method signature for method %s cannot contain an @RegionData parameter and a different Region type parameter",
method.getName()));
if (regionDataAnnotationParameterPosition >= 0 && regionTypeParameterPosition >= 0) {
Assert.isTrue(regionDataAnnotationParameterPosition == regionTypeParameterPosition, String.format(
"Function method signature for method %s cannot contain an @RegionData parameter and a different Region type parameter",
method.getName()));
}
int tempRegionParameterPosition = -1;
regionParameterPosition = (regionDataAnnotationParameterPosition >= 0 ? regionDataAnnotationParameterPosition
: (regionTypeParameterPosition >= 0 ? regionTypeParameterPosition : -1));
if (annotatedRegionDataParameterPosition >= 0) {
tempRegionParameterPosition = annotatedRegionDataParameterPosition;
} else if (regionTypeParameterPosition >= 0) {
tempRegionParameterPosition = regionTypeParameterPosition;
}
regionParameterPosition = tempRegionParameterPosition;
filterParameterPosition = GemfireFunctionUtils.getAnnotationParameterPosition(method, Filter.class,
new Class[] { Set.class });
functionContextParameterPosition = getArgumentTypePosition(method, FunctionContext.class);
resultSenderParameterPosition = getArgumentTypePosition(method, ResultSender.class);
new Class[] { Set.class });
if (regionParameterPosition >= 0 && filterParameterPosition >= 0) {
Assert.state(regionParameterPosition != filterParameterPosition,
"region parameter and filter parameter must be different");
"region parameter and filter parameter must be different");
}
functionContextParameterPosition = getArgumentTypePosition(method, FunctionContext.class);
resultSenderParameterPosition = getArgumentTypePosition(method, ResultSender.class);
}
@Override
public Method getFunctionAnnotatedMethod() {
return method;
}
@Override
public Object[] resolveFunctionArguments(FunctionContext functionContext) {
Object[] args = super.resolveFunctionArguments(functionContext);
if (functionContext instanceof RegionFunctionContext) {
if (this.regionParameterPosition >= 0) {
args = ArrayUtils.insert(args, regionParameterPosition,
getRegionForContext((RegionFunctionContext) functionContext));
args = ArrayUtils.insert(args, regionParameterPosition, getRegionForContext(
(RegionFunctionContext) functionContext));
}
if (this.filterParameterPosition >= 0) {
args = ArrayUtils.insert(args, filterParameterPosition,
((RegionFunctionContext) functionContext).getFilter());
((RegionFunctionContext) functionContext).getFilter());
}
}
if (this.functionContextParameterPosition >= 0) {
args = ArrayUtils.insert(args, functionContextParameterPosition, functionContext);
}
@@ -105,47 +105,53 @@ class FunctionContextInjectingArgumentResolver extends DefaultFunctionArgumentRe
}
Assert.isTrue(args.length == method.getParameterTypes().length, String.format(
"wrong number of arguments for method %s. Expected :%d, actual: %d", method.getName(),
"wrong number of arguments for method %s. Expected %d, but was %d", method.getName(),
method.getParameterTypes().length, args.length));
return args;
}
/*
* @param regionFunctionContext
* @return
* (non-Javadoc)
* @see com.gemstone.gemfire.cache.execute.RegionFunctionContext
*/
private static Region<?, ?> getRegionForContext(RegionFunctionContext regionFunctionContext) {
Region<?, ?> region = regionFunctionContext.getDataSet();
if (PartitionRegionHelper.isPartitionedRegion(region)) {
if (logger.isDebugEnabled()) {
logger.debug("this is a partitioned region - filtering local data for context");
}
region = PartitionRegionHelper.getLocalDataForContext(regionFunctionContext);
}
if (logger.isDebugEnabled()) {
logger.debug("region contains " + region.size() + " items");
}
return region;
}
/*
* (non-Javadoc)
*/
private static int getArgumentTypePosition(Method method, Class<?> requiredType) {
int index = 0;
int position = -1;
int i = 0;
for (Class<?> clazz : method.getParameterTypes()) {
if (requiredType.equals(clazz)) {
Assert.state(
position < 0,
String.format("Method %s signature cannot contain more than one parameter of type %s.",
method.getName(), requiredType.getName()));
position = i;
}
i++;
}
return position;
for (Class<?> parameterType : method.getParameterTypes()) {
if (requiredType.equals(parameterType)) {
Assert.state(position < 0, String.format(
"Method %s signature cannot contain more than one parameter of type %s.",
method.getName(), requiredType.getName()));
position = index;
}
index++;
}
return position;
}
}

View File

@@ -10,6 +10,7 @@
* 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.function;
import java.lang.annotation.Annotation;
@@ -26,23 +27,23 @@ import org.springframework.util.StringUtils;
import com.gemstone.gemfire.cache.execute.FunctionService;
/**
*
* @author David Turanski
*
*/
public abstract class GemfireFunctionUtils {
private static Log log = LogFactory.getLog(GemfireFunctionUtils.class);
/**
* Wrap a target object and method in a GemFire Function and register the function to the {@link FunctionService}
*
* Wrap a target object and method in a GemFire Function and register the function to the {@link FunctionService}
*
* @param target the target object
* @param method the method bound to the function
* @param attributes function attributes
* @param overwrite if true, will replace the existing function
*/
public static void registerFunctionForPojoMethod(Object target, Method method, Map<String, Object> attributes,
boolean overwrite) {
boolean overwrite) {
String id = attributes.containsKey("id") ? (String) attributes.get("id") : "";
PojoFunctionWrapper function = new PojoFunctionWrapper(target, method, id);
@@ -57,18 +58,15 @@ public abstract class GemfireFunctionUtils {
if (attributes.containsKey("batchSize")) {
int batchSize = (Integer) attributes.get("batchSize");
Assert.isTrue(
batchSize >= 0,
String.format("batchSize must be a non-negative value %s.%s", target.getClass().getName(),
method.getName()));
Assert.isTrue(batchSize >= 0, String.format("batchSize must be a non-negative value %1$s.%2$s",
target.getClass().getName(), method.getName()));
function.setBatchSize(batchSize);
}
if (attributes.containsKey("hasResult")) {
boolean hasResult = (Boolean) attributes.get("hasResult");
//Only set if true
if (hasResult) {
function.setHasResult(hasResult);
// only set if true TODO figure out why???
if (Boolean.TRUE.equals(attributes.get("hasResult"))) {
function.setHasResult(true);
}
}
@@ -86,7 +84,8 @@ public abstract class GemfireFunctionUtils {
if (log.isDebugEnabled()) {
log.debug("registered function " + function.getId());
}
} else {
}
else {
if (log.isDebugEnabled()) {
log.debug("function " + function.getId() + "is already registered");
}
@@ -95,7 +94,7 @@ public abstract class GemfireFunctionUtils {
/**
* Determine the order position of a an annotated method parameter
*
*
* @param method the {@link Method} instance
* @param targetAnnotationType the annotation
* @param requiredTypes an array of valid parameter types for the annotation
@@ -103,42 +102,48 @@ public abstract class GemfireFunctionUtils {
*/
public static int getAnnotationParameterPosition(Method method, Class<?> targetAnnotationType,
Class<?>[] requiredTypes) {
int position = -1;
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
if (parameterAnnotations.length > 0) {
int position = -1;
Class<?>[] paramTypes = method.getParameterTypes();
Class<?>[] parameterTypes = method.getParameterTypes();
List<Class<?>> requiredTypesList = Arrays.asList(requiredTypes);
for (int i = 0; i < parameterAnnotations.length; i++) {
Annotation[] annotations = parameterAnnotations[i];
for (int index = 0; index < parameterAnnotations.length; index++) {
Annotation[] annotations = parameterAnnotations[index];
if (annotations.length > 0) {
for (Annotation annotation : annotations) {
if (annotation.annotationType().equals(targetAnnotationType)) {
Assert.state(
position < 0,
String.format(
"Method %s signature cannot contain more than one parameter annotated with type %s",
method.getName(), targetAnnotationType.getName()));
Assert.state(position < 0, String.format(
"Method %s signature cannot contain more than one parameter annotated with type %s",
method.getName(), targetAnnotationType.getName()));
boolean isRequiredType = false;
for (Class<?> requiredType: requiredTypesList) {
if (requiredType.isAssignableFrom(paramTypes[i])) {
for (Class<?> requiredType : requiredTypesList) {
if (requiredType.isAssignableFrom(parameterTypes[index])) {
isRequiredType = true;
break;
}
}
Assert.isTrue(isRequiredType, String.format(
"Parameter of type %s annotated with %s must be assignable from one of type %s in method %s",
paramTypes[i], targetAnnotationType.getName(),
StringUtils.arrayToCommaDelimitedString(requiredTypes), method.getName()));
position = i;
}
Assert.isTrue(isRequiredType, String.format(
"Parameter of type %s annotated with %s must be assignable from one of type %s in method %s",
parameterTypes[index], targetAnnotationType.getName(),
StringUtils.arrayToCommaDelimitedString(requiredTypes), method.getName()));
position = index;
}
}
}
}
return position;
}
return -1;
return position;
}
}

View File

@@ -0,0 +1,130 @@
/*
* Copyright 2010-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.function;
import java.lang.reflect.Method;
import org.springframework.util.ClassUtils;
import com.gemstone.gemfire.cache.CacheClosedException;
import com.gemstone.gemfire.cache.CacheFactory;
import com.gemstone.gemfire.cache.execute.FunctionContext;
import com.gemstone.gemfire.pdx.PdxInstance;
/**
* The PdxFunctionArgumentResolver class is a Spring Data GemFire FunctionArgumentResolver that automatically resolves
* PDX types when GemFire is configured with read-serialized set to true, but the application domain classes
* are actually on the classpath.
*
* @author John Blum
* @see org.springframework.data.gemfire.function.DefaultFunctionArgumentResolver
* @see com.gemstone.gemfire.pdx.PdxInstance
* @since 1.5.2
*/
@SuppressWarnings("unused")
class PdxFunctionArgumentResolver extends DefaultFunctionArgumentResolver {
/*
* (non-Javadoc)
*
* @see com.gemstone.gemfire.cache.execute.FunctionContext
*/
@Override
public Object[] resolveFunctionArguments(final FunctionContext functionContext) {
Object[] functionArguments = super.resolveFunctionArguments(functionContext);
if (isPdxSerializerConfigured()) {
int index = 0;
for (Object functionArgument : functionArguments) {
if (functionArgument instanceof PdxInstance) {
String className = ((PdxInstance) functionArgument).getClassName();
if (isDeserializationNecessary(className)) {
functionArguments[index] = ((PdxInstance) functionArgument).getObject();
}
}
index++;
}
}
return functionArguments;
}
/*
* (non-Javadoc)
*
* @see java.lang.reflect.Method
*/
@Override
public Method getFunctionAnnotatedMethod() {
throw new UnsupportedOperationException("Not Implemented!");
}
/*
* (non-Javadoc)
*
* @see com.gemstone.gemfire.cache.Cache#getPdxSerializer()
* @see com.gemstone.gemfire.cache.CacheFactory#getAnyInstance()
*/
boolean isPdxSerializerConfigured() {
try {
return (CacheFactory.getAnyInstance().getPdxSerializer() != null);
}
catch (CacheClosedException ignore) {
return false;
}
}
/*
* (non-Javadac)
*
* @see #isOnClasspath(String)
* @see #functionAnnotatedMethodHasParameterOfType(String)
*/
boolean isDeserializationNecessary(final String className) {
return (isOnClasspath(className) && functionAnnotatedMethodHasParameterOfType(className));
}
/*
* (non-Javadoc)
*
* @see java.lang.Thread#currentThread()
* @see java.lang.Thread#getContextClassLoader()
* @see org.springframework.util.ClassUtils#isPresent(String, ClassLoader)
*/
boolean isOnClasspath(final String className) {
return ClassUtils.isPresent(className, Thread.currentThread().getContextClassLoader());
}
/*
* (non-Javadoc)
*
* @see #getFunctionAnnotatedMethod()
* @see java.lang.reflect.Method#getParameterTypes()
*/
boolean functionAnnotatedMethodHasParameterOfType(final String className) {
for (Class<?> parameterType : getFunctionAnnotatedMethod().getParameterTypes()) {
if (parameterType.getName().equals(className)) {
return true;
}
}
return false;
}
}

View File

@@ -41,74 +41,70 @@ public class PojoFunctionWrapper implements Function {
private static transient Log logger = LogFactory.getLog(PojoFunctionWrapper.class);
private volatile boolean HA;
private volatile boolean optimizeForWrite;
private volatile boolean hasResult;
private final Object target;
private final Method method;
private final String id;
private volatile boolean optimizeForWrite;
private volatile int batchSize;
private final FunctionArgumentResolver functionArgumentResolver;
private final Method method;
private final Object target;
private final String id;
public PojoFunctionWrapper(Object target, Method method, String id) {
this.functionArgumentResolver = new FunctionContextInjectingArgumentResolver(method);
this.id = StringUtils.hasText(id) ? id : method.getName();
this.target = target;
this.method = method;
this.id = (StringUtils.hasText(id) ? id : method.getName());
this.HA = false;
this.hasResult = !(method.getReturnType().equals(void.class));
this.optimizeForWrite = false;
}
//@Override
public String getId() {
return this.id;
}
//@Override
public boolean hasResult() {
return this.hasResult;
}
//@Override
public boolean isHA() {
return this.HA;
}
public void setHA(boolean HA) {
this.HA = HA;
}
//@Override
public boolean optimizeForWrite() {
return this.optimizeForWrite;
}
public void setOptimizeForWrite(boolean optimizeForWrite) {
this.optimizeForWrite = optimizeForWrite;
}
public void setBatchSize(int batchSize) {
this.batchSize = batchSize;
}
public void setHA(boolean HA) {
this.HA = HA;
}
@Override
public boolean isHA() {
return this.HA;
}
public void setHasResult(boolean hasResult) {
this.hasResult = hasResult;
}
//@Override
public void execute(FunctionContext functionContext) {
@Override
public boolean hasResult() {
return this.hasResult;
}
@Override
public String getId() {
return this.id;
}
public void setOptimizeForWrite(boolean optimizeForWrite) {
this.optimizeForWrite = optimizeForWrite;
}
@Override
public boolean optimizeForWrite() {
return this.optimizeForWrite;
}
@Override
public void execute(final FunctionContext functionContext) {
Object[] args = this.functionArgumentResolver.resolveFunctionArguments(functionContext);
Object result = null;
result = invokeTargetMethod(args);
Object result = invokeTargetMethod(args);
if (hasResult()) {
sendResults(functionContext.getResultSender(), result);
@@ -116,32 +112,33 @@ public class PojoFunctionWrapper implements Function {
}
protected final Object invokeTargetMethod(Object[] args) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("about to invoke method %s on class %s as function %s", method.getName(), target
.getClass().getName(), this.id));
logger.debug(String.format("about to invoke method %s on class %s as function %s", method.getName(),
target.getClass().getName(), this.id));
for (Object arg : args) {
logger.debug("arg:" + arg.getClass().getName() + " " + arg.toString());
}
}
return (Object) ReflectionUtils.invokeMethod(method, target, (Object[]) args);
return ReflectionUtils.invokeMethod(method, target, (Object[]) args);
}
private void sendResults(ResultSender<Object> resultSender, Object result) {
if (result == null) {
resultSender.lastResult(null);
return;
}
if (ObjectUtils.isArray(result)) {
new BatchingResultSender(batchSize, resultSender).sendArrayResults(result);
} else if (Iterable.class.isAssignableFrom(result.getClass())) {
new BatchingResultSender(batchSize, resultSender).sendResults((Iterable<?>) result);
} else {
resultSender.lastResult(result);
else {
if (ObjectUtils.isArray(result)) {
new BatchingResultSender(batchSize, resultSender).sendArrayResults(result);
}
else if (Iterable.class.isAssignableFrom(result.getClass())) {
new BatchingResultSender(batchSize, resultSender).sendResults((Iterable<?>) result);
}
else {
resultSender.lastResult(result);
}
}
}
}