Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2002-2020 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.function;
import static java.util.Arrays.stream;
import static org.springframework.data.gemfire.util.ArrayUtils.nullSafeArray;
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalArgumentException;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.geode.cache.execute.Function;
import org.apache.geode.cache.execute.FunctionService;
import org.apache.geode.security.ResourcePermission;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.gemfire.function.annotation.GemfireFunction;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* Utility class for registering a POJO as a Pivotal GemFire/Apache Geode {@link Function}.
*
* @author David Turanski
* @author John Blum
* @see org.apache.geode.cache.execute.Function
* @see org.apache.geode.cache.execute.FunctionService
* @see org.springframework.data.gemfire.function.annotation.GemfireFunction
* @since 1.2.0
*/
public abstract class GemfireFunctionUtils {
private static final String DEFAULT_FUNCTION_ID = null;
private static Logger logger = LoggerFactory.getLogger(GemfireFunctionUtils.class);
/**
* Determines whether the given {@link Method} is a POJO, {@link GemfireFunction} annotated {@link Method}.
*
* @param method {@link Method} to evaluate.
* @return a boolean value indicating whether the {@link Method} on a POJO represents a SDG {@link GemfireFunction}.
* @see org.springframework.data.gemfire.function.annotation.GemfireFunction
* @see java.lang.reflect.Method
*/
public static boolean isGemfireFunction(Method method) {
return method != null && method.isAnnotationPresent(GemfireFunction.class);
}
/**
* Determines whether the given {@link Method} is a POJO, {@link GemfireFunction} annotated {@link Method}
* having an ID matching the given {@code functionId}.
*
* @param method {@link Method} to evaluate.
* @param functionId {@link String} containing the {@link Function#getId() ID} to match.
* @return a boolean indicating whether the given {@link Method} is a POJO, {@link GemfireFunction} annotated
* {@link Method} with the given {@code functionId}.
* @see #getGemfireFunctionId(Method)
* @see java.lang.reflect.Method
*/
public static boolean isMatchingGemfireFunction(Method method, String functionId) {
return getGemfireFunctionId(method).filter(methodFunctionId ->
ObjectUtils.nullSafeEquals(methodFunctionId, functionId)).isPresent();
}
static Annotation getAnnotation(AnnotatedElement element, Class annotationType) {
return AnnotationUtils.getAnnotation(element, annotationType);
}
static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element, Annotation annotation) {
return AnnotationAttributes.fromMap(AnnotationUtils.getAnnotationAttributes(element, annotation));
}
static AnnotationAttributes getAnnotationAttributes(AnnotatedElement element,
Class annotationType) {
return element.isAnnotationPresent(annotationType)
? getAnnotationAttributes(element, getAnnotation(element, annotationType))
: null;
}
/**
* Null-safe operation used to determine the Pivotal GemFire/Apache Geode {@link Function#getId()} Function ID}
* of a given {@link GemfireFunction} annotated POJO {@link Method}.
*
* If the {@link Method} is not {@literal null} and annotated with {@link GemfireFunction} then this method
* tries to determine the {@link Function#getId()} from the {@link GemfireFunction#id()} attribute.
*
* If the {@link GemfireFunction#id()} attribute was not explicitly set, then the {@link Method#getName()}
* is returned as the {@link Function#getId() Function ID}.
*
* @param method {@link GemfireFunction} annotated POJO {@link Method} containing the implementation
* of the GemFire/Geode {@link Function}.
* @return the GemFire/Geode {@link Function#getId() Function ID} of the given {@link Method},
* or an empty {@link Optional} if the {@link Method} is not a GemFire/Geode {@link Function}.
* @see org.springframework.data.gemfire.function.annotation.GemfireFunction
* @see java.lang.reflect.Method
* @see #isGemfireFunction(Method)
*/
@SuppressWarnings("all")
public static Optional getGemfireFunctionId(Method method) {
return Optional.ofNullable(method)
.filter(GemfireFunctionUtils::isGemfireFunction)
.map(it ->
Optional.ofNullable(getAnnotationAttributes(it, GemfireFunction.class))
.filter(annotationAttributes -> annotationAttributes.containsKey("id"))
.map(annotationAttributes -> annotationAttributes.getString("id"))
.filter(StringUtils::hasText)
.orElseGet(() -> it.getName())
);
}
private static Object constructInstance(Class type, Object... constructorArguments) {
return org.springframework.data.util.ReflectionUtils.findConstructor(type, constructorArguments)
.map(constructor -> BeanUtils.instantiateClass(constructor, constructorArguments))
.orElseThrow(() -> newIllegalArgumentException(
"No suitable constructor was found for type [%s] having parameters [%s]", type.getName(),
stream(nullSafeArray(constructorArguments, Object.class))
.map(ObjectUtils::nullSafeClassName)
.collect(Collectors.toList())));
}
private static String nullSafeName(Method method) {
return Optional.ofNullable(method)
.map(it -> String.format("%s.%s", method.getDeclaringClass().getName(), method.getName()))
.orElse(null);
}
/**
* Bind a {@link Method method} with the given {@link Function#getId() Function ID} on an object
* of the given {@link Class type} as a {@link Function} and register it with the {@link FunctionService}.
*
* @param type {@link Class target type} to evaluate; must not be {@literal null}.
* @param functionId {@link String} containing the {@link Function#getId()} identifying the {@link Method}
* on the {@link Class target type} to bind as a {@link Function}.
* @throws IllegalArgumentException if {@link Class type} is {@literal null}.
* @see #registerFunctionForPojoMethod(Object, Method, AnnotationAttributes, boolean)
* @see #isMatchingGemfireFunction(Method, String)
*/
@SuppressWarnings("unused")
public static void registerFunctionForPojoMethod(Class type, String functionId) {
Assert.notNull(type, () -> String.format("Class type of POJO containing %s(s) is required",
GemfireFunction.class.getName()));
ReflectionUtils.doWithMethods(type,
method -> registerFunctionForPojoMethod(constructInstance(type), method,
getAnnotationAttributes(method, GemfireFunction.class), true),
method -> isMatchingGemfireFunction(method, functionId));
}
/**
* Bind a {@link Method method} with the given {@link Function#getId() Function ID} on the given {@link Object target}
* as a {@link Function} and register it with the {@link FunctionService}.
*
* @param target {@link Object target object} to evaluate; must not be {@literal null}.
* @param functionId {@link String} containing the {@link Function#getId()} identifying the {@link Method}
* on the {@link Object target object} to bind as a {@link Function}.
* @throws IllegalArgumentException if {@link Object target} is {@literal null}.
* @see #registerFunctionForPojoMethod(Object, Method, AnnotationAttributes, boolean)
* @see #isMatchingGemfireFunction(Method, String)
*/
@SuppressWarnings("unused")
public static void registerFunctionForPojoMethod(Object target, String functionId) {
Assert.notNull(target, "Target object is required");
ReflectionUtils.doWithMethods(target.getClass(),
method -> registerFunctionForPojoMethod(target, method,
getAnnotationAttributes(method, GemfireFunction.class), true),
method -> isMatchingGemfireFunction(method, functionId));
}
/**
* Binds the given {@link Method method} on the given {@link Object target} as a {@link Function}
* and registers it with the {@link FunctionService}.
*
* @param target {@link Object target object} to evaluate; must not be {@literal null}.
* @param method {@link Method} on {@link Object target} bound as a {@link Function}.
* @param overwrite if {@literal true}, will replace any existing {@link Function}
* having the same {@link Function#getId() ID}.
* @throws IllegalArgumentException if {@link Object target} is {@literal null} or the given {@link Method}
* is not a {@link GemfireFunction}.
* @see #registerFunctionForPojoMethod(Object, Method, AnnotationAttributes, boolean)
* @see #isGemfireFunction(Method)
*/
@SuppressWarnings("unused")
public static void registerFunctionForPojoMethod(Object target, Method method, boolean overwrite) {
Assert.notNull(target, "Target object is required");
Assert.isTrue(isGemfireFunction(method), () -> String.format("Method [%s] must be a %s",
nullSafeName(method), GemfireFunction.class.getName()));
registerFunctionForPojoMethod(target, method, getAnnotationAttributes(method, GemfireFunction.class), overwrite);
}
/**
* Wrap the {@link Object target object} and {@link Method method} in a Pivotal GemFire/Apache Geode {@link Function}
* and register the {@link Function} with the {@link FunctionService}.
*
* @param target {@link Object target object}.
* @param method {@link Method} bound to a {@link Function}.
* @param gemfireFunctionAttributes {@link GemfireFunction} {@link AnnotationAttributes annotation attributes}.
* @param overwrite if {@literal true}, will replace any existing {@link Function} having the same ID.
*/
public static void registerFunctionForPojoMethod(Object target, Method method,
AnnotationAttributes gemfireFunctionAttributes, boolean overwrite) {
PojoFunctionWrapper function =
new PojoFunctionWrapper(target, method, resolveFunctionId(gemfireFunctionAttributes));
configureBatchSize(target, method, gemfireFunctionAttributes, function);
configureHighAvailability(gemfireFunctionAttributes, function);
configureHasResult(gemfireFunctionAttributes, function);
configureOptimizeForWrite(gemfireFunctionAttributes, function);
configureRequiredPermissions(gemfireFunctionAttributes, function);
doFunctionRegistration(function, overwrite);
}
static String resolveFunctionId(AnnotationAttributes gemfireFunctionAttributes) {
return gemfireFunctionAttributes.containsKey("id")
? gemfireFunctionAttributes.getString("id")
: DEFAULT_FUNCTION_ID;
}
static void configureBatchSize(Object target, Method method, AnnotationAttributes gemfireFunctionAttributes,
PojoFunctionWrapper function) {
if (gemfireFunctionAttributes.containsKey("batchSize")) {
int batchSize = gemfireFunctionAttributes.getNumber("batchSize");
Assert.isTrue(batchSize >= 0,
String.format("%1$s.batchSize [%2$d] specified on [%3$s.%4$s] must be a non-negative value",
GemfireFunction.class.getSimpleName(), batchSize, target.getClass().getName(), method.getName()));
function.setBatchSize(batchSize);
}
}
static void configureHighAvailability(AnnotationAttributes gemfireFunctionAttributes,
PojoFunctionWrapper function) {
if (gemfireFunctionAttributes.containsKey("HA")) {
function.setHA(gemfireFunctionAttributes.getBoolean("HA"));
}
}
static void configureHasResult(AnnotationAttributes gemfireFunctionAttributes, PojoFunctionWrapper function) {
if (gemfireFunctionAttributes.containsKey("hasResult")) {
if (Boolean.TRUE.equals(gemfireFunctionAttributes.getBoolean("hasResult"))) {
function.setHasResult(true);
}
}
}
static void configureOptimizeForWrite(AnnotationAttributes gemfireFunctionAttributes,
PojoFunctionWrapper function) {
if (gemfireFunctionAttributes.containsKey("optimizeForWrite")) {
function.setOptimizeForWrite(gemfireFunctionAttributes.getBoolean("optimizeForWrite"));
}
}
static void configureRequiredPermissions(AnnotationAttributes gemfireFunctionAttributes,
PojoFunctionWrapper function) {
String[] requiredPermissionStrings = nullSafeArray(gemfireFunctionAttributes.containsKey("requiredPermissions")
? gemfireFunctionAttributes.getStringArray("requiredPermissions")
: null, String.class);
Stream requiredPermissionsStream = Arrays.stream(requiredPermissionStrings);
Optional.of(requiredPermissionsStream
.filter(StringUtils::hasText)
.map(GemfireFunctionUtils::parseResourcePermission)
.collect(Collectors.toList()))
.filter(requiredPermissions -> !requiredPermissions.isEmpty())
.ifPresent(function::setRequiredPermissions);
}
static ResourcePermission parseResourcePermission(String resourcePermissionString) {
Assert.hasText(resourcePermissionString,
String.format("ResourcePermission [%s] is required", resourcePermissionString));
ResourcePermission.Resource resource = ResourcePermission.Resource.DATA;
ResourcePermission.Operation operation = ResourcePermission.Operation.WRITE;
String key = null;
String target = null;
String[] resourcePermissionStringComponents = resourcePermissionString.split(":");
if (resourcePermissionStringComponents.length > 0) {
resource = parseEnum(resourcePermissionStringComponents[0], ResourcePermission.Resource.class,
ResourcePermission.Resource.values());
}
if (resourcePermissionStringComponents.length > 1) {
operation = parseEnum(resourcePermissionStringComponents[1], ResourcePermission.Operation.class,
ResourcePermission.Operation.values());
}
if (resourcePermissionStringComponents.length > 2) {
target = nullSafeTrim(resourcePermissionStringComponents[2]);
}
if (resourcePermissionStringComponents.length > 3) {
key = nullSafeTrim(resourcePermissionStringComponents[3]);
}
return new ResourcePermission(resource, operation, target, key);
}
private static String nullSafeToUpperCase(String value) {
return StringUtils.hasText(value) ? value.trim().toUpperCase() : "null";
}
private static String nullSafeTrim(String value) {
return value != null ? value.trim() : null;
}
private static > T parseEnum(String name, Class enumType, Enum[] enumeratedValues) {
name = nullSafeToUpperCase(name);
try {
return Enum.valueOf(enumType, name);
}
catch (IllegalArgumentException cause) {
throw newIllegalArgumentException(cause, "[%1$s] is not a valid [%2$s] type; must be 1 of %3$s",
name, enumType.getName(), Arrays.toString(enumeratedValues));
}
}
static void doFunctionRegistration(PojoFunctionWrapper function, boolean overwrite) {
if (FunctionService.isRegistered(function.getId())) {
if (overwrite) {
if (logger.isDebugEnabled()) {
logger.debug("Overwrite enabled; Unregistering Function [{}]", function.getId());
}
FunctionService.unregisterFunction(function.getId());
}
}
if (FunctionService.isRegistered(function.getId())) {
if (logger.isDebugEnabled()) {
logger.debug("Function [{}] is already registered", function.getId());
}
}
else {
FunctionService.registerFunction(function);
if (logger.isDebugEnabled()) {
logger.debug("Registered Function [{}]", function.getId());
}
}
}
/**
* 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
* @return the parameter position or -1 if the annotated parameter is not found
*/
public static int getAnnotationParameterPosition(Method method, Class targetAnnotationType,
Class[] requiredTypes) {
int position = -1;
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
if (parameterAnnotations.length > 0) {
Class[] parameterTypes = method.getParameterTypes();
List> requiredTypesList = Arrays.asList(requiredTypes);
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()));
boolean isRequiredType = false;
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",
parameterTypes[index], targetAnnotationType.getName(),
StringUtils.arrayToCommaDelimitedString(requiredTypes), method.getName()));
position = index;
}
}
}
}
}
return position;
}
}