From d3b31a6f6bb2f4aa994e31fcfac7173ff29c7156 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakousky Date: Tue, 6 Aug 2019 14:23:34 +0200 Subject: [PATCH] Added initial support for loading functions by type in new deployer --- .../BeanFactoryAwareFunctionRegistry.java | 4 ++ .../context/catalog/FunctionTypeUtils.java | 24 +++++++++++ .../deployer/ApplicationContainer.java | 8 ++++ .../deployer/ExternalFunctionJarLauncher.java | 40 +++++++++++++++++++ .../function/deployer/FunctionProperties.java | 17 ++++++-- .../deployer/sample/SampleInvoker.java | 23 ++++++----- 6 files changed, 104 insertions(+), 12 deletions(-) diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistry.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistry.java index 775ad9c8d..3f91ec3a4 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistry.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwareFunctionRegistry.java @@ -194,6 +194,10 @@ public class BeanFactoryAwareFunctionRegistry Assert.isTrue(functionNames.length == 1, "Found more then one function in BeanFactory"); definition = functionNames[0]; } + else { + Assert.isTrue(this.registrationsByName.size() == 1, "Found more then one function in local registry"); + definition = this.registrationsByName.keySet().iterator().next(); + } } return definition; } diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtils.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtils.java index 5c5d83031..2fa58c03e 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtils.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/FunctionTypeUtils.java @@ -16,6 +16,7 @@ package org.springframework.cloud.function.context.catalog; +import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.function.Consumer; @@ -41,6 +42,29 @@ public final class FunctionTypeUtils { } + public static Type getFunctionTypeFromFunctionMethod(Method functionMethod) { + Assert.isTrue( + functionMethod.getName().equals("apply") || + functionMethod.getName().equals("accept") || + functionMethod.getName().equals("get"), + "Only Supplier, Function or Consumer supported at the moment. Was " + functionMethod.getDeclaringClass()); + + if (functionMethod.getName().equals("apply")) { + return ResolvableType.forClassWithGenerics(Function.class, + ResolvableType.forMethodParameter(functionMethod, 0), + ResolvableType.forMethodReturnType(functionMethod)).getType(); + + } + else if (functionMethod.getName().equals("accept")) { + return ResolvableType.forClassWithGenerics(Consumer.class, + ResolvableType.forMethodParameter(functionMethod, 0)).getType(); + } + else { + return ResolvableType.forClassWithGenerics(Supplier.class, + ResolvableType.forMethodReturnType(functionMethod)).getType(); + } + } + public static Type getFunctionType(Object function, FunctionInspector inspector) { FunctionRegistration registration = inspector.getRegistration(function); if (registration != null) { diff --git a/spring-cloud-function-deployer-new/src/main/java/org/springframework/cloud/function/deployer/ApplicationContainer.java b/spring-cloud-function-deployer-new/src/main/java/org/springframework/cloud/function/deployer/ApplicationContainer.java index ef6271087..5bf6cecf6 100644 --- a/spring-cloud-function-deployer-new/src/main/java/org/springframework/cloud/function/deployer/ApplicationContainer.java +++ b/spring-cloud-function-deployer-new/src/main/java/org/springframework/cloud/function/deployer/ApplicationContainer.java @@ -32,11 +32,14 @@ public abstract class ApplicationContainer { private final FunctionProperties functionProperties; + private final Object function; + public ApplicationContainer(FunctionCatalog functionCatalog, FunctionInspector functionInspector, FunctionProperties functionProperties) { this.functionCatalog = functionCatalog; this.functionInspector = functionInspector; this.functionProperties = functionProperties; + this.function = this.functionCatalog.lookup(this.functionProperties.getFunctionName()); } protected FunctionCatalog getFunctionCatalog() { @@ -50,4 +53,9 @@ public abstract class ApplicationContainer { protected FunctionProperties getFunctionProperties() { return this.functionProperties; } + + @SuppressWarnings("unchecked") + public T getFunction() { + return (T) this.function; + } } diff --git a/spring-cloud-function-deployer-new/src/main/java/org/springframework/cloud/function/deployer/ExternalFunctionJarLauncher.java b/spring-cloud-function-deployer-new/src/main/java/org/springframework/cloud/function/deployer/ExternalFunctionJarLauncher.java index a7a1ac900..507f8f832 100644 --- a/spring-cloud-function-deployer-new/src/main/java/org/springframework/cloud/function/deployer/ExternalFunctionJarLauncher.java +++ b/spring-cloud-function-deployer-new/src/main/java/org/springframework/cloud/function/deployer/ExternalFunctionJarLauncher.java @@ -22,6 +22,7 @@ import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -33,12 +34,17 @@ import org.springframework.boot.loader.archive.Archive; import org.springframework.boot.loader.jar.JarFile; import org.springframework.cloud.function.context.FunctionRegistration; import org.springframework.cloud.function.context.FunctionRegistry; +import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; import org.springframework.context.ApplicationContext; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardTypeLocator; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.ReflectionUtils.MethodCallback; +import org.springframework.util.ReflectionUtils.MethodFilter; import org.springframework.util.StreamUtils; +import org.springframework.util.StringUtils; /** * @@ -80,6 +86,10 @@ class ExternalFunctionJarLauncher extends JarLauncher { registration.type(type); functionRegistry.register(registration); } + FunctionRegistration registration = this.discovereAndLoadFunctionFromClassName(deployerContext.getBean(FunctionProperties.class)); + if (registration != null) { + functionRegistry.register(registration); + } } catch (Exception e) { throw new IllegalStateException("Failed to deploy archive " + archive, e); @@ -114,6 +124,36 @@ class ExternalFunctionJarLauncher extends JarLauncher { }; } + private FunctionRegistration discovereAndLoadFunctionFromClassName(FunctionProperties functionProperties) throws Exception { + FunctionRegistration functionRegistration = null; + AtomicReference typeRef = new AtomicReference<>(); + if (StringUtils.hasText(functionProperties.getFunctionClass())) { + Class functionClass = Thread.currentThread().getContextClassLoader().loadClass(functionProperties.getFunctionClass()); + + ReflectionUtils.doWithMethods(functionClass, new MethodCallback() { + @Override + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + typeRef.set(FunctionTypeUtils.getFunctionTypeFromFunctionMethod(method)); + } + }, new MethodFilter() { + @Override + public boolean matches(Method method) { + String name = method.getName(); + return typeRef.get() == null && ("apply".equals(name) || "accept".equals(name) || "get".equals(name)); + } + }); + + if (typeRef.get() != null) { + Object functionInstance = functionClass.newInstance(); + + functionRegistration = new FunctionRegistration<>(functionInstance, + StringUtils.uncapitalize(functionClass.getSimpleName())); + functionRegistration.type(typeRef.get()); + } + } + return functionRegistration; + } + private void launch(ApplicationContext deployerContext, String[] args) throws Exception { JarFile.registerUrlProtocolHandler(); Thread.currentThread().setContextClassLoader(createClassLoader(getClassPathArchives())); diff --git a/spring-cloud-function-deployer-new/src/main/java/org/springframework/cloud/function/deployer/FunctionProperties.java b/spring-cloud-function-deployer-new/src/main/java/org/springframework/cloud/function/deployer/FunctionProperties.java index b47c0f94d..d399a6563 100644 --- a/spring-cloud-function-deployer-new/src/main/java/org/springframework/cloud/function/deployer/FunctionProperties.java +++ b/spring-cloud-function-deployer-new/src/main/java/org/springframework/cloud/function/deployer/FunctionProperties.java @@ -20,6 +20,7 @@ import javax.annotation.PostConstruct; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * Configuration properties for deciding how to locate the functional class to execute. @@ -36,11 +37,21 @@ public class FunctionProperties { private String functionName; - public void setFunctionName(String functionName) { - this.functionName = functionName; + private String functionClass; + + public void setFunctionClass(String functionClass) { + this.functionClass = functionClass; } - public String getName() { + public String getFunctionClass() { + return this.functionClass; + } + + public void setFunctionName(String functionName) { + this.functionName = StringUtils.hasText(functionName) ? functionName : ""; + } + + public String getFunctionName() { return this.functionName; } diff --git a/spring-cloud-function-deployer-new/src/test/java/org/springframework/cloud/function/deployer/sample/SampleInvoker.java b/spring-cloud-function-deployer-new/src/test/java/org/springframework/cloud/function/deployer/sample/SampleInvoker.java index 27eb7bb89..4aa562b7b 100644 --- a/spring-cloud-function-deployer-new/src/test/java/org/springframework/cloud/function/deployer/sample/SampleInvoker.java +++ b/spring-cloud-function-deployer-new/src/test/java/org/springframework/cloud/function/deployer/sample/SampleInvoker.java @@ -31,27 +31,32 @@ import org.springframework.cloud.function.deployer.FunctionProperties; */ public class SampleInvoker extends ApplicationContainer { - public static void main(String[] args) throws Exception { - SampleInvoker invoker = FunctionDeployerBootstrap.instance( - "--spring.cloud.function.location=/Users/olegz/Downloads/simple-function-app/target/simple-function-app-0.0.1-SNAPSHOT.jar", + SampleInvoker invokerByClass = FunctionDeployerBootstrap.instance( + "--spring.cloud.function.location=/Users/olegz/Downloads/simple-function-app/jars/simple-function-jar-0.0.1-SNAPSHOT.jar", + "--spring.cloud.function.function-class=oz.function.simplefunctionapp.UpperCaseFunction") + .run(SampleInvoker.class, args); + + System.out.println(invokerByClass.uppercase("eric")); + System.out.println(invokerByClass.uppercase("oleg")); + + SampleInvoker invokerByBean = FunctionDeployerBootstrap.instance( + "--spring.cloud.function.location=/Users/olegz/Downloads/simple-function-app/jars/simple-function-app-0.0.1-SNAPSHOT.jar", "--spring.cloud.function.function-name=uppercase") .run(SampleInvoker.class, args); - System.out.println(invoker.uppercase("eric")); - System.out.println(invoker.uppercase("oleg")); + System.out.println(invokerByBean.uppercase("eric")); + System.out.println(invokerByBean.uppercase("oleg")); } - private Function function; - public SampleInvoker(FunctionCatalog functionCatalog, FunctionInspector functionInspector, FunctionProperties functionProperties) { super(functionCatalog, functionInspector, functionProperties); - this.function = this.getFunctionCatalog().lookup(functionProperties.getName()); } public String uppercase(String value) { - return this.function.apply(value); + Function functon = this.getFunction(); + return functon.apply(value); } }