/** * Get the name of the method including the package path built from {@link Class#getCanonicalName()} and * {@link Method#getName()}. e.g. {@code com.fnproject.fn.api.MethodWrapper::getLongName} for this method. * * @return long method name */ default String getLongName() { return getTargetClass().getCanonicalName() + "::" + getTargetMethod().getName(); }
@Override public Optional<InputEvent> tryCoerceParam(InvocationContext currentContext, int arg, InputEvent input, MethodWrapper method) { if (method.getParamType(arg).getParameterClass().equals(InputEvent.class)) { return Optional.of(input); } else { return Optional.empty(); } } }
@Override public Optional<OutputEvent> wrapFunctionResult(InvocationContext ctx, MethodWrapper method, Object value) { if (method.getReturnType().getParameterClass().equals(OutputEvent.class)) { return Optional.of((OutputEvent) value); } else { return Optional.empty(); } } }
/** * Get the number of parameters the method has. * * @return Parameter count */ default int getParameterCount() { return getTargetMethod().getParameterTypes().length; } }
static Class<?> resolveType(Type type, MethodWrapper src) { if (type instanceof Class) { return PrimitiveTypeResolver.resolve((Class<?>) type); } else if (type instanceof ParameterizedType) { return (Class<?>) ((ParameterizedType) type).getRawType(); } Class<?> resolvedType = TypeResolver.resolveRawArgument(type, src.getTargetClass()); if (resolvedType == TypeResolver.Unknown.class) { // TODO: Decide what exception to throw here throw new RuntimeException("Cannot infer type of method parameter"); } else { return resolvedType; } }
private Object[] coerceParameters(InvocationContext ctx, MethodWrapper targetMethod, InputEvent evt) { try { Object[] userFunctionParams = new Object[targetMethod.getParameterCount()]; for (int paramIndex = 0; paramIndex < userFunctionParams.length; paramIndex++) { userFunctionParams[paramIndex] = coerceParameter(ctx, targetMethod, paramIndex, evt); } return userFunctionParams; } catch (RuntimeException e) { throw new FunctionInputHandlingException("An exception was thrown during Input Coercion: " + e.getMessage(), e); } }
@Override public List<InputCoercion> getInputCoercions(MethodWrapper targetMethod, int param) { Annotation parameterAnnotations[] = targetMethod.getTargetMethod().getParameterAnnotations()[param]; Optional<Annotation> coercionAnnotation = Arrays.stream(parameterAnnotations) .filter((ann) -> ann.annotationType().equals(InputBinding.class)) .findFirst(); if (coercionAnnotation.isPresent()) { try { List<InputCoercion> coercionList = new ArrayList<>(); InputBinding inputBindingAnnotation = (InputBinding) coercionAnnotation.get(); coercionList.add(inputBindingAnnotation.coercion().getDeclaredConstructor().newInstance()); return coercionList; } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) { throw new FunctionInputHandlingException("Unable to instantiate input coercion class for argument " + param + " of " + targetMethod); } } List<InputCoercion> inputList = new ArrayList<>(); inputList.addAll(userInputCoercions); inputList.addAll(builtinInputCoercions); return inputList; }
private void applyUserConfigurationMethod(MethodWrapper targetClass, FunctionRuntimeContext runtimeContext) { Arrays.stream(targetClass.getTargetClass().getMethods()) .filter(this::isConfigurationMethod) .sorted(Comparator.<Method>comparingInt((m) -> Modifier.isStatic(m.getModifiers()) ? 0 : 1) // run static methods first .thenComparing(Comparator.<Method>comparingInt((m) -> { // depth first in implementation int depth = 0; Class<?> cc = m.getDeclaringClass(); while (null != cc) { depth++; cc = cc.getSuperclass(); } return depth; }))) .forEach(configMethod -> { try { Optional<Object> fnInstance = runtimeContext.getInvokeInstance(); // Allow the runtime context parameter to be optional if (configMethod.getParameterCount() == 0) { configMethod.invoke(fnInstance.orElse(null)); } else { configMethod.invoke(fnInstance.orElse(null), runtimeContext); } } catch ( InvocationTargetException e){ throw new FunctionConfigurationException("Error invoking configuration method: " + configMethod.getName(), e.getCause()); } catch (IllegalAccessException e) { throw new FunctionConfigurationException("Error invoking configuration method: " + configMethod.getName(), e); } }); }
protected Object[] coerceParameters(InvocationContext ctx, MethodWrapper targetMethod, InputEvent evt) { try { Object[] userFunctionParams = new Object[targetMethod.getParameterCount()]; for (int paramIndex = 0; paramIndex < userFunctionParams.length; paramIndex++) { userFunctionParams[paramIndex] = coerceParameter(ctx, targetMethod, paramIndex, evt); } return userFunctionParams; } catch (RuntimeException e) { throw new FunctionInputHandlingException("An exception was thrown during Input Coercion: " + e.getMessage(), e); } }
private void validateConfigurationMethods(MethodWrapper function) { // Function configuration methods must have a void return type. Arrays.stream(function.getTargetClass().getMethods()) .filter(this::isConfigurationMethod) .filter((m) -> !m.getReturnType().equals(Void.TYPE)) .forEach((m) -> { throw new FunctionConfigurationException("Configuration method '" + m.getName() + "' does not have a void return type"); }); // If target method is static, configuration methods cannot be non-static. if (Modifier.isStatic(function.getTargetMethod().getModifiers())) { Arrays.stream(function.getTargetClass().getMethods()) .filter(this::isConfigurationMethod) .filter((m) -> !(Modifier.isStatic(m.getModifiers()))) .forEach((m) -> { throw new FunctionConfigurationException("Configuration method '" + m.getName() + "' cannot be an instance method if the function method is a static method"); }); } }
public static TypeWrapper fromReturnType(MethodWrapper method) { return new MethodTypeWrapper(resolveType(method.getTargetMethod().getGenericReturnType(), method)); }
@Override public void initialize(RuntimeContext ctx) { ctx.addInvoker(new SpringCloudFunctionInvoker(ctx.getMethod().getTargetClass()),FunctionInvoker.Phase.Call); } }
@Override public Optional<OutputEvent> wrapFunctionResult(InvocationContext ctx, MethodWrapper method, Object value) { if (method.getReturnType().getParameterClass().equals(String.class)) { return Optional.of(OutputEvent.fromBytes(((String) value).getBytes(), OutputEvent.Status.Success, "text/plain")); } else { return Optional.empty(); } }
@Override public Optional<String> tryCoerceParam(InvocationContext currentContext, int param, InputEvent input, MethodWrapper method) { if (method.getParamType(param).getParameterClass().equals(String.class)) { return Optional.of( input.consumeBody(is -> { try { return IOUtils.toString(is, StandardCharsets.UTF_8); } catch (IOException e) { throw new RuntimeException("Error reading input as string"); } })); } else { return Optional.empty(); } } }
@Override public Optional<Object> getInvokeInstance() { if (!Modifier.isStatic(getMethod().getTargetMethod().getModifiers())) { if (instance == null) { try { Constructor<?> constructors[] = getMethod().getTargetClass().getConstructors(); if (constructors.length == 1) { Constructor<?> ctor = constructors[0]; instance = ctor.newInstance(FunctionRuntimeContext.this); } else { if (getMethod().getTargetClass().getEnclosingClass() != null && !Modifier.isStatic(getMethod().getTargetClass().getModifiers())) { throw new FunctionClassInstantiationException("The function " + getMethod().getTargetClass() + " cannot be instantiated as it is a non-static inner class"); } else { throw new FunctionClassInstantiationException("The function " + getMethod().getTargetClass() + " cannot be instantiated as its constructor takes an unrecognized argument of type " + constructors[0].getParameterTypes()[0] + ". Function classes should have a single public constructor that takes either no arguments or a RuntimeContext argument"); throw new FunctionClassInstantiationException("The function " + getMethod().getTargetClass() + " cannot be instantiated as its constructor takes more than one argument. Function classes should have a single public constructor that takes either no arguments or a RuntimeContext argument"); throw new FunctionClassInstantiationException("The function " + getMethod().getTargetClass() + " cannot be instantiated as it has no public constructors. Function classes should have a single public constructor that takes either no arguments or a RuntimeContext argument"); } else { throw new FunctionClassInstantiationException("The function " + getMethod().getTargetClass() + " cannot be instantiated as it has multiple public constructors. Function classes should have a single public constructor that takes either no arguments or a RuntimeContext argument"); throw new FunctionClassInstantiationException("An error occurred in the function constructor while instantiating " + getMethod().getTargetClass(), e.getCause()); } catch (InstantiationException | IllegalAccessException e) { throw new FunctionClassInstantiationException("The function class " + getMethod().getTargetClass() + " could not be instantiated", e);
public static TypeWrapper fromParameter(MethodWrapper method, int paramIndex) { return new MethodTypeWrapper(resolveType(method.getTargetMethod().getGenericParameterTypes()[paramIndex], method)); }
FnFeature f = method.getTargetClass().getAnnotation(FnFeature.class); if (f != null) { enableFeature(runtimeContext, f); FnFeatures fs = method.getTargetClass().getAnnotation(FnFeatures.class); if (fs != null) { for (FnFeature fnFeature : fs.value()) {
public Optional<OutputEvent> wrapFunctionResult(InvocationContext ctx, MethodWrapper method, Object value) { if (method.getReturnType().getParameterClass().equals(byte[].class)) { return Optional.of(OutputEvent.fromBytes(((byte[]) value), OutputEvent.Status.Success, "application/octet-stream")); } else { return Optional.empty(); } }
@Override public Optional<Object> tryCoerceParam(InvocationContext currentContext, int arg, InputEvent input, MethodWrapper method) { Class<?> paramClass = method.getParamType(arg).getParameterClass(); if (paramClass.equals(RuntimeContext.class)) { return Optional.of(currentContext.getRuntimeContext()); } else if (paramClass.equals(InvocationContext.class)) { return Optional.of(currentContext); } else if (paramClass.equals(HTTPGatewayContext.class)) { return Optional.of(new FunctionHTTPGatewayContext(currentContext)); } else { return Optional.empty(); } } }
private Optional<OutputEvent> coerceReturnValue(InvocationContext ctx, MethodWrapper method, Object rawResult) { try { return Optional.of((ctx.getRuntimeContext()).getOutputCoercions(method.getTargetMethod()) .stream() .map((c) -> c.wrapFunctionResult(ctx, method, rawResult)) .filter(Optional::isPresent) .map(Optional::get) .findFirst() .orElseThrow(() -> new FunctionOutputHandlingException("No coercion found for return type"))); } catch (RuntimeException e) { throw new FunctionOutputHandlingException("An exception was thrown during Output Coercion: " + e.getMessage(), e); } }