diff --git a/src/main/java/com/microsoft/azure/functions/worker/binding/BindingDataStore.java b/src/main/java/com/microsoft/azure/functions/worker/binding/BindingDataStore.java index 36cbd491..ca7542a9 100644 --- a/src/main/java/com/microsoft/azure/functions/worker/binding/BindingDataStore.java +++ b/src/main/java/com/microsoft/azure/functions/worker/binding/BindingDataStore.java @@ -47,15 +47,8 @@ public void addTriggerMetadataSource(Map metadata) { } } - public void addExecutionContextSource(String invocationId, String funcname, ExecutionTraceContext traceContext, ExecutionRetryContext retryContext) { - otherSources.put(ExecutionContext.class, - new ExecutionContextDataSource( - invocationId, - funcname, - traceContext, - retryContext - ) - ); + public void addExecutionContextSource(ExecutionContextDataSource executionContextDataSource) { + otherSources.put(ExecutionContext.class,executionContextDataSource); } public Optional getDataByName(String name, Type target) { diff --git a/src/main/java/com/microsoft/azure/functions/worker/binding/ExecutionContextDataSource.java b/src/main/java/com/microsoft/azure/functions/worker/binding/ExecutionContextDataSource.java index 5fce9584..48cd82c4 100644 --- a/src/main/java/com/microsoft/azure/functions/worker/binding/ExecutionContextDataSource.java +++ b/src/main/java/com/microsoft/azure/functions/worker/binding/ExecutionContextDataSource.java @@ -1,23 +1,50 @@ package com.microsoft.azure.functions.worker.binding; -import java.util.logging.Logger; - import com.microsoft.azure.functions.ExecutionContext; -import com.microsoft.azure.functions.worker.WorkerLogManager; -import com.microsoft.azure.functions.TraceContext; import com.microsoft.azure.functions.RetryContext; +import com.microsoft.azure.functions.TraceContext; +import com.microsoft.azure.functions.rpc.messages.ParameterBinding; +import com.microsoft.azure.functions.rpc.messages.TypedData; +import com.microsoft.azure.functions.worker.WorkerLogManager; +import com.microsoft.azure.functions.worker.broker.MethodBindInfo; +import com.microsoft.azure.functions.worker.broker.ParamBindInfo; -final class ExecutionContextDataSource extends DataSource implements ExecutionContext { - ExecutionContextDataSource(String invocationId, String funcname, TraceContext traceContext, RetryContext retryContext) { +import java.lang.reflect.Parameter; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +public final class ExecutionContextDataSource extends DataSource implements ExecutionContext { + + private final String invocationId; + private final TraceContext traceContext; + private final RetryContext retryContext; + private final Logger logger; + private final String funcname; + private final BindingDataStore dataStore; + private final MethodBindInfo methodBindInfo; + private final Class containingClass; + + ExecutionContextDataSource(Builder builder){ super(null, null, EXECONTEXT_DATA_OPERATIONS); - this.invocationId = invocationId; - this.traceContext = traceContext; - this.retryContext = retryContext; - this.logger = WorkerLogManager.getInvocationLogger(invocationId); - this.funcname = funcname; + this.invocationId = builder.invocationId; + this.traceContext = builder.traceContext; + this.retryContext = builder.retryContext; + this.logger = WorkerLogManager.getInvocationLogger(this.invocationId); + this.funcname = builder.funcname; + this.dataStore = builder.dataStore; + this.methodBindInfo = builder.methodBindInfo; + this.containingClass = builder.containingClass; this.setValue(this); } + private static final DataOperations EXECONTEXT_DATA_OPERATIONS = new DataOperations<>(); + static { + EXECONTEXT_DATA_OPERATIONS.addGenericOperation(ExecutionContext.class, DataOperations::generalAssignment); + } + @Override public String getInvocationId() { return this.invocationId; } @@ -32,15 +59,65 @@ final class ExecutionContextDataSource extends DataSource impl @Override public String getFunctionName() { return this.funcname; } - - private final String invocationId; - private final TraceContext traceContext; - private final RetryContext retryContext; - private final Logger logger; - private final String funcname; - private static final DataOperations EXECONTEXT_DATA_OPERATIONS = new DataOperations<>(); - static { - EXECONTEXT_DATA_OPERATIONS.addGenericOperation(ExecutionContext.class, DataOperations::generalAssignment); - } + public BindingDataStore getDataStore() { + return dataStore; + } + + public MethodBindInfo getMethodBindInfo() { + return methodBindInfo; + } + + public Class getContainingClass() { + return containingClass; + } + + public static class Builder{ + private String invocationId; + private TraceContext traceContext; + private RetryContext retryContext; + private String funcname; + private BindingDataStore dataStore; + private MethodBindInfo methodBindInfo; + private Class containingClass; + + public Builder invocationId(String invocationId){ + this.invocationId = invocationId; + return this; + } + + public Builder traceContext(TraceContext traceContext){ + this.traceContext = traceContext; + return this; + } + + public Builder retryContext(RetryContext retryContext){ + this.retryContext = retryContext; + return this; + } + + public Builder funcname(String funcname){ + this.funcname = funcname; + return this; + } + + public Builder dataStore(BindingDataStore dataStore){ + this.dataStore = dataStore; + return this; + } + + public Builder methodBindInfo(MethodBindInfo methodBindInfo){ + this.methodBindInfo = methodBindInfo; + return this; + } + + public Builder containingClass(Class containingClass){ + this.containingClass = containingClass; + return this; + } + + public ExecutionContextDataSource build(){ + return new ExecutionContextDataSource(this); + } + } } \ No newline at end of file diff --git a/src/main/java/com/microsoft/azure/functions/worker/broker/CoreTypeResolver.java b/src/main/java/com/microsoft/azure/functions/worker/broker/CoreTypeResolver.java index f033937b..73a283ae 100644 --- a/src/main/java/com/microsoft/azure/functions/worker/broker/CoreTypeResolver.java +++ b/src/main/java/com/microsoft/azure/functions/worker/broker/CoreTypeResolver.java @@ -121,7 +121,7 @@ static String getBindingNameAnnotation(Parameter param) { return new String(""); } - static boolean checkHasImplicitOutput(Parameter parameter) { + static boolean checkImplicitOutput(Parameter parameter) { Annotation[] annotations = parameter.getAnnotations(); for (Annotation annotation : annotations) { for (Annotation item : annotation.annotationType().getAnnotations()) { diff --git a/src/main/java/com/microsoft/azure/functions/worker/broker/FunctionDefinition.java b/src/main/java/com/microsoft/azure/functions/worker/broker/FunctionDefinition.java new file mode 100644 index 00000000..a5915350 --- /dev/null +++ b/src/main/java/com/microsoft/azure/functions/worker/broker/FunctionDefinition.java @@ -0,0 +1,74 @@ +package com.microsoft.azure.functions.worker.broker; + +import com.microsoft.azure.functions.rpc.messages.BindingInfo; +import com.microsoft.azure.functions.worker.binding.BindingDefinition; +import com.microsoft.azure.functions.worker.description.FunctionMethodDescriptor; +import com.microsoft.azure.functions.worker.reflect.ClassLoaderProvider; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This Class encapsulate data about functions defined by customer. + * Each function defined by customer will be used to build one FunctionDefinition object accordingly. + */ +public class FunctionDefinition { + private final Class containingClass; + private final Map bindingDefinitions; + private final MethodBindInfo candidate; + + public FunctionDefinition(FunctionMethodDescriptor descriptor, Map bindingInfos, ClassLoaderProvider classLoaderProvider) + throws ClassNotFoundException, NoSuchMethodException + { + descriptor.validateMethodInfo(); + + this.containingClass = getContainingClass(descriptor.getFullClassName(), classLoaderProvider); + + this.candidate = getCandidate(descriptor, containingClass); + + this.bindingDefinitions = new HashMap<>(); + + for (Map.Entry entry : bindingInfos.entrySet()) { + this.bindingDefinitions.put(entry.getKey(), new BindingDefinition(entry.getKey(), entry.getValue())); + } + } + + private static Class getContainingClass(String className, ClassLoaderProvider classLoaderProvider) throws ClassNotFoundException { + ClassLoader classLoader = classLoaderProvider.createClassLoader(); + return Class.forName(className, false, classLoader); + } + + private static MethodBindInfo getCandidate(FunctionMethodDescriptor descriptor, Class containingClass) throws NoSuchMethodException { + List candidates = new ArrayList<>(); + for (Method method : containingClass.getMethods()) { + if (method.getDeclaringClass().getName().equals(descriptor.getFullClassName()) && method.getName().equals(descriptor.getMethodName())) { + candidates.add(new MethodBindInfo(method)); + } + } + + if (candidates.isEmpty()) { + throw new NoSuchMethodException("There are no methods named \"" + descriptor.getName() + "\" in class \"" + descriptor.getFullClassName() + "\""); + } + + if (candidates.size() > 1) { + throw new UnsupportedOperationException("Found more than one function with method name \"" + descriptor.getName() + "\" in class \"" + descriptor.getFullClassName() + "\""); + } + + return candidates.get(0); + } + + public Class getContainingClass() { + return containingClass; + } + + public Map getBindingDefinitions() { + return bindingDefinitions; + } + + public MethodBindInfo getCandidate() { + return candidate; + } +} diff --git a/src/main/java/com/microsoft/azure/functions/worker/broker/FunctionMethodExecutorImpl.java b/src/main/java/com/microsoft/azure/functions/worker/broker/FunctionMethodExecutorImpl.java index c4a4da13..a399d2c0 100644 --- a/src/main/java/com/microsoft/azure/functions/worker/broker/FunctionMethodExecutorImpl.java +++ b/src/main/java/com/microsoft/azure/functions/worker/broker/FunctionMethodExecutorImpl.java @@ -1,70 +1,88 @@ package com.microsoft.azure.functions.worker.broker; +import java.lang.invoke.WrongMethodTypeException; import java.lang.reflect.*; import java.util.*; +import com.microsoft.azure.functions.OutputBinding; import com.microsoft.azure.functions.worker.binding.*; -import com.microsoft.azure.functions.worker.description.*; -import com.microsoft.azure.functions.worker.reflect.*; -import com.microsoft.azure.functions.rpc.messages.*; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.commons.lang3.reflect.TypeUtils; /** * Used to executor of arbitrary Java method in any JAR using reflection. * Thread-Safety: Multiple thread. */ public class FunctionMethodExecutorImpl implements JavaMethodExecutor { - public FunctionMethodExecutorImpl(FunctionMethodDescriptor descriptor, Map bindingInfos, ClassLoaderProvider classLoaderProvider) - throws ClassNotFoundException, NoSuchMethodException - { - descriptor.validateMethodInfo(); - this.classLoader = classLoaderProvider.createClassLoader(); - this.containingClass = getContainingClass(descriptor.getFullClassName()); - this.overloadResolver = new ParameterResolver(); - - for (Method method : this.containingClass.getMethods()) { - if (method.getDeclaringClass().getName().equals(descriptor.getFullClassName()) && method.getName().equals(descriptor.getMethodName())) { - this.overloadResolver.addCandidate(method); - } - } - - if (!this.overloadResolver.hasCandidates()) { - throw new NoSuchMethodException("There are no methods named \"" + descriptor.getName() + "\" in class \"" + descriptor.getFullClassName() + "\""); - } - - if (this.overloadResolver.hasMultipleCandidates()) { - throw new UnsupportedOperationException("Found more than one function with method name \"" + descriptor.getName() + "\" in class \"" + descriptor.getFullClassName() + "\""); - } - - this.bindingDefinitions = new HashMap<>(); + private final ClassLoader classLoader; - for (Map.Entry entry : bindingInfos.entrySet()) { - this.bindingDefinitions.put(entry.getKey(), new BindingDefinition(entry.getKey(), entry.getValue())); - } + public FunctionMethodExecutorImpl(ClassLoader classLoader) { + this.classLoader = classLoader; } - public Map getBindingDefinitions() { return this.bindingDefinitions; } - - public ParameterResolver getOverloadResolver() { return this.overloadResolver; } - - public void execute(BindingDataStore dataStore) throws Exception { + public void execute(ExecutionContextDataSource executionContextDataSource) throws Exception { try { Thread.currentThread().setContextClassLoader(this.classLoader); - Object retValue = this.overloadResolver.resolve(dataStore) + Object retValue = this.resolveArguments(executionContextDataSource) .orElseThrow(() -> new NoSuchMethodException("Cannot locate the method signature with the given input")) - .invoke(() -> this.containingClass.newInstance()); - dataStore.setDataTargetValue(BindingDataStore.RETURN_NAME, retValue); + .invoke(() -> executionContextDataSource.getContainingClass().newInstance()); + executionContextDataSource.getDataStore().setDataTargetValue(BindingDataStore.RETURN_NAME, retValue); } finally { Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader()); } } - private Class getContainingClass(String className) throws ClassNotFoundException { - return Class.forName(className, false, this.classLoader); + private synchronized Optional resolveArguments(ExecutionContextDataSource executionContextDataSource) { + InvokeInfoBuilder invoker = this.resolve(executionContextDataSource); + if (invoker != null) { + executionContextDataSource.getDataStore().promoteDataTargets(invoker.getOutputsId()); + return Optional.of(invoker.build()); + } + return Optional.empty(); } - private final Class containingClass; - private final ClassLoader classLoader; - private final ParameterResolver overloadResolver; - private final Map bindingDefinitions; + private InvokeInfoBuilder resolve(ExecutionContextDataSource executionContextDataSource) { + try { + MethodBindInfo method = executionContextDataSource.getMethodBindInfo(); + BindingDataStore dataStore = executionContextDataSource.getDataStore(); + final InvokeInfoBuilder invokeInfo = new InvokeInfoBuilder(method); + for (ParamBindInfo param : method.getParams()) { + String paramName = param.getName(); + Type paramType = param.getType(); + String paramBindingNameAnnotation = param.getBindingNameAnnotation(); + Optional argument; + if (OutputBinding.class.isAssignableFrom(TypeUtils.getRawType(paramType, null))) { + argument = dataStore.getOrAddDataTarget(invokeInfo.getOutputsId(), paramName, paramType, false); + } + else if (paramName != null && !paramName.isEmpty()) { + argument = dataStore.getDataByName(paramName, paramType); + } + else if (paramName == null && !paramBindingNameAnnotation.isEmpty()) { + argument = dataStore.getTriggerMetatDataByName(paramBindingNameAnnotation, paramType); + } + else { + argument = dataStore.getDataByType(paramType); + } + BindingData actualArg = argument.orElseThrow(WrongMethodTypeException::new); + invokeInfo.appendArgument(actualArg.getValue()); + } + if (!method.getEntry().getReturnType().equals(void.class) && !method.getEntry().getReturnType().equals(Void.class)) { + dataStore.getOrAddDataTarget(invokeInfo.getOutputsId(), BindingDataStore.RETURN_NAME, method.getEntry().getReturnType(), method.isImplicitOutput()); + } + return invokeInfo; + } catch (Exception ex) { + ExceptionUtils.rethrow(ex); + return null; + } + } + + public static final class InvokeInfoBuilder extends JavaMethodInvokeInfo.Builder { + private InvokeInfoBuilder(MethodBindInfo method) { super.setMethod(method.getEntry()); } + private final UUID outputsId = UUID.randomUUID(); + + private UUID getOutputsId() { + return outputsId; + } + } } diff --git a/src/main/java/com/microsoft/azure/functions/worker/broker/JavaFunctionBroker.java b/src/main/java/com/microsoft/azure/functions/worker/broker/JavaFunctionBroker.java index 52ae9ba0..eea32513 100644 --- a/src/main/java/com/microsoft/azure/functions/worker/broker/JavaFunctionBroker.java +++ b/src/main/java/com/microsoft/azure/functions/worker/broker/JavaFunctionBroker.java @@ -11,6 +11,7 @@ import com.microsoft.azure.functions.rpc.messages.*; import com.microsoft.azure.functions.worker.Constants; import com.microsoft.azure.functions.worker.binding.BindingDataStore; +import com.microsoft.azure.functions.worker.binding.ExecutionContextDataSource; import com.microsoft.azure.functions.worker.binding.ExecutionRetryContext; import com.microsoft.azure.functions.worker.binding.ExecutionTraceContext; import com.microsoft.azure.functions.worker.description.FunctionMethodDescriptor; @@ -32,34 +33,40 @@ public JavaFunctionBroker(ClassLoaderProvider classLoaderProvider) { public void loadMethod(FunctionMethodDescriptor descriptor, Map bindings) throws ClassNotFoundException, NoSuchMethodException, IOException { descriptor.validate(); - addSearchPathsToClassLoader(descriptor); - JavaMethodExecutor executor = new FunctionMethodExecutorImpl(descriptor, bindings, classLoaderProvider); - - this.methods.put(descriptor.getId(), new ImmutablePair<>(descriptor.getName(), executor)); + FunctionDefinition functionDefinition = new FunctionDefinition(descriptor, bindings, classLoaderProvider); + this.methods.put(descriptor.getId(), new ImmutablePair<>(descriptor.getName(), functionDefinition)); } public Optional invokeMethod(String id, InvocationRequest request, List outputs) throws Exception { - ImmutablePair methodEntry = this.methods.get(id); - JavaMethodExecutor executor = methodEntry.right; - if (executor == null) { + ExecutionContextDataSource executionContextDataSource = buildExecutionContext(id, request); + FunctionMethodExecutorImpl executor = new FunctionMethodExecutorImpl(this.classLoaderProvider.createClassLoader()); + executor.execute(executionContextDataSource); + outputs.addAll(executionContextDataSource.getDataStore().getOutputParameterBindings(true)); + return executionContextDataSource.getDataStore().getDataTargetTypedValue(BindingDataStore.RETURN_NAME); + } + + private ExecutionContextDataSource buildExecutionContext(String id, InvocationRequest request) + throws NoSuchMethodException { + ImmutablePair methodEntry = this.methods.get(id); + FunctionDefinition functionDefinition = methodEntry.right; + if (functionDefinition == null) { throw new NoSuchMethodException("Cannot find method with ID \"" + id + "\""); } - BindingDataStore dataStore = new BindingDataStore(); - dataStore.setBindingDefinitions(executor.getBindingDefinitions()); + dataStore.setBindingDefinitions(functionDefinition.getBindingDefinitions()); dataStore.addTriggerMetadataSource(getTriggerMetadataMap(request)); dataStore.addParameterSources(request.getInputDataList()); - ExecutionTraceContext traceContext = new ExecutionTraceContext(request.getTraceContext().getTraceParent(), request.getTraceContext().getTraceState(), request.getTraceContext().getAttributesMap()); ExecutionRetryContext retryContext = new ExecutionRetryContext(request.getRetryContext().getRetryCount(), request.getRetryContext().getMaxRetryCount(), request.getRetryContext().getException()); - - dataStore.addExecutionContextSource(request.getInvocationId(), methodEntry.left, traceContext, retryContext); - - executor.execute(dataStore); - outputs.addAll(dataStore.getOutputParameterBindings(true)); - return dataStore.getDataTargetTypedValue(BindingDataStore.RETURN_NAME); + ExecutionContextDataSource.Builder executionContextDataSourceBuilder = new ExecutionContextDataSource.Builder(); + ExecutionContextDataSource executionContextDataSource = executionContextDataSourceBuilder + .invocationId(request.getInvocationId()).funcname(methodEntry.left).traceContext(traceContext) + .retryContext(retryContext).dataStore(dataStore).methodBindInfo(functionDefinition.getCandidate()) + .containingClass(functionDefinition.getContainingClass()).build(); + dataStore.addExecutionContextSource(executionContextDataSource); + return executionContextDataSource; } public Optional getMethodName(String id) { @@ -151,7 +158,7 @@ public void setWorkerDirectory(String workerDirectory) { this.workerDirectory = workerDirectory; } - private final Map> methods; + private final Map> methods; private final ClassLoaderProvider classLoaderProvider; private String workerDirectory; } diff --git a/src/main/java/com/microsoft/azure/functions/worker/broker/JavaMethodExecutor.java b/src/main/java/com/microsoft/azure/functions/worker/broker/JavaMethodExecutor.java index 4928cadd..7cd16ce1 100644 --- a/src/main/java/com/microsoft/azure/functions/worker/broker/JavaMethodExecutor.java +++ b/src/main/java/com/microsoft/azure/functions/worker/broker/JavaMethodExecutor.java @@ -10,9 +10,5 @@ * Thread-Safety: Multiple thread. */ public interface JavaMethodExecutor { - Map getBindingDefinitions(); - - ParameterResolver getOverloadResolver(); - - void execute(BindingDataStore dataStore) throws Exception; + void execute(ExecutionContextDataSource executionContextDataSource) throws Exception; } diff --git a/src/main/java/com/microsoft/azure/functions/worker/broker/MethodBindInfo.java b/src/main/java/com/microsoft/azure/functions/worker/broker/MethodBindInfo.java new file mode 100644 index 00000000..42e71389 --- /dev/null +++ b/src/main/java/com/microsoft/azure/functions/worker/broker/MethodBindInfo.java @@ -0,0 +1,37 @@ +package com.microsoft.azure.functions.worker.broker; + +import java.lang.reflect.Method; +import java.util.Arrays; + +public final class MethodBindInfo { + + private final Method entry; + private final ParamBindInfo[] params; + private final boolean isImplicitOutput; + MethodBindInfo(Method m) { + this.entry = m; + this.params = Arrays.stream(this.entry.getParameters()).map(ParamBindInfo::new).toArray(ParamBindInfo[]::new); + this.isImplicitOutput = checkImplicitOutput(params); + } + + private static boolean checkImplicitOutput(ParamBindInfo[] params){ + for (ParamBindInfo paramBindInfo : params){ + if (paramBindInfo.isImplicitOutput()) return true; + } + return false; + } + + public Method getEntry() { + return entry; + } + + public ParamBindInfo[] getParams() { + return params; + } + + public boolean isImplicitOutput() { + return isImplicitOutput; + } +} + + diff --git a/src/main/java/com/microsoft/azure/functions/worker/broker/ParamBindInfo.java b/src/main/java/com/microsoft/azure/functions/worker/broker/ParamBindInfo.java new file mode 100644 index 00000000..a0c2d12d --- /dev/null +++ b/src/main/java/com/microsoft/azure/functions/worker/broker/ParamBindInfo.java @@ -0,0 +1,40 @@ +package com.microsoft.azure.functions.worker.broker; + +import java.lang.reflect.Parameter; +import java.lang.reflect.Type; + +public final class ParamBindInfo { + + private final String name; + private final Type type; + private final String bindingNameAnnotation; + private final boolean isImplicitOutput; + private final Parameter parameter; + ParamBindInfo(Parameter param) { + this.name = CoreTypeResolver.getAnnotationName(param); + this.type = param.getParameterizedType(); + this.bindingNameAnnotation = CoreTypeResolver.getBindingNameAnnotation(param); + this.isImplicitOutput = CoreTypeResolver.checkImplicitOutput(param); + this.parameter = param; + } + + public boolean isImplicitOutput() { + return isImplicitOutput; + } + + public String getName() { + return name; + } + + public Type getType() { + return type; + } + + public String getBindingNameAnnotation() { + return bindingNameAnnotation; + } + + public Parameter getParameter() { + return parameter; + } +} diff --git a/src/main/java/com/microsoft/azure/functions/worker/broker/ParameterResolver.java b/src/main/java/com/microsoft/azure/functions/worker/broker/ParameterResolver.java deleted file mode 100644 index df7d4bde..00000000 --- a/src/main/java/com/microsoft/azure/functions/worker/broker/ParameterResolver.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.microsoft.azure.functions.worker.broker; - -import java.lang.invoke.WrongMethodTypeException; -import java.lang.reflect.Method; -import java.lang.reflect.Parameter; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.apache.commons.lang3.reflect.TypeUtils; - -import com.microsoft.azure.functions.OutputBinding; -import com.microsoft.azure.functions.worker.binding.BindingData; -import com.microsoft.azure.functions.worker.binding.BindingDataStore; - -/** - * Resolve a Java method overload using reflection. - * Thread-Safety: Multiple thread. - */ -public class ParameterResolver { - ParameterResolver() { - this.candidates = new ArrayList<>(); - } - - synchronized void addCandidate(Method method) { - this.candidates.add(new MethodBindInfo(method)); - } - - public synchronized boolean hasCandidates() { - return !this.candidates.isEmpty(); - } - - public synchronized boolean hasMultipleCandidates() { - return this.candidates.size() > 1; - } - - synchronized Optional resolve(BindingDataStore dataStore) { - InvokeInfoBuilder invoker = this.resolve(this.candidates.get(0), dataStore); - if (invoker != null) { - dataStore.promoteDataTargets(invoker.outputsId); - return Optional.of(invoker.build()); - } - return Optional.empty(); - } - - private InvokeInfoBuilder resolve(MethodBindInfo method, BindingDataStore dataStore) { - try { - final InvokeInfoBuilder invokeInfo = new InvokeInfoBuilder(method); - for (ParamBindInfo param : method.params) { - Optional argument = null; - if (OutputBinding.class.isAssignableFrom(TypeUtils.getRawType(param.type, null))) { - argument = dataStore.getOrAddDataTarget(invokeInfo.outputsId, param.name, param.type, false); - } - else if (param.name != null && !param.name.isEmpty()) { - argument = dataStore.getDataByName(param.name, param.type); - } - else if (param.name == null && !param.bindingNameAnnotation.isEmpty()) { - argument = dataStore.getTriggerMetatDataByName(param.bindingNameAnnotation, param.type); - } - else { - argument = dataStore.getDataByType(param.type); - } - BindingData actualArg = argument.orElseThrow(WrongMethodTypeException::new); - invokeInfo.appendArgument(actualArg.getValue()); - } - if (!method.entry.getReturnType().equals(void.class) && !method.entry.getReturnType().equals(Void.class)) { - dataStore.getOrAddDataTarget(invokeInfo.outputsId, BindingDataStore.RETURN_NAME, method.entry.getReturnType(), method.hasImplicitOutput); - } - return invokeInfo; - } catch (Exception ex) { - ExceptionUtils.rethrow(ex); - return null; - } - } - - private final class InvokeInfoBuilder extends JavaMethodInvokeInfo.Builder { - InvokeInfoBuilder(MethodBindInfo method) { super.setMethod(method.entry); } - private final UUID outputsId = UUID.randomUUID(); - } - - private final class MethodBindInfo { - MethodBindInfo(Method m) { - this.entry = m; - this.params = Arrays.stream(this.entry.getParameters()).map(ParamBindInfo::new).toArray(ParamBindInfo[]::new); - this.hasImplicitOutput = checkHasImplicitOutput(); - } - - private boolean checkHasImplicitOutput(){ - for (ParamBindInfo paramBindInfo : this.params){ - if (paramBindInfo.hasImplicitOutput) return true; - } - return false; - } - - private final Method entry; - private final ParamBindInfo[] params; - private final boolean hasImplicitOutput; - } - - private final class ParamBindInfo { - ParamBindInfo(Parameter param) { - this.name = CoreTypeResolver.getAnnotationName(param); - this.type = param.getParameterizedType(); - this.bindingNameAnnotation = CoreTypeResolver.getBindingNameAnnotation(param); - this.hasImplicitOutput = CoreTypeResolver.checkHasImplicitOutput(param); - } - - private final String name; - private final Type type; - private String bindingNameAnnotation = new String(""); - private final boolean hasImplicitOutput; - } - - private final List candidates; -} diff --git a/src/test/java/com/microsoft/azure/functions/worker/broker/JavaMethodExecutorTest.java b/src/test/java/com/microsoft/azure/functions/worker/broker/FunctionDefinitionTest.java similarity index 71% rename from src/test/java/com/microsoft/azure/functions/worker/broker/JavaMethodExecutorTest.java rename to src/test/java/com/microsoft/azure/functions/worker/broker/FunctionDefinitionTest.java index 8c896737..8df0d293 100644 --- a/src/test/java/com/microsoft/azure/functions/worker/broker/JavaMethodExecutorTest.java +++ b/src/test/java/com/microsoft/azure/functions/worker/broker/FunctionDefinitionTest.java @@ -6,8 +6,6 @@ import java.io.File; import java.lang.reflect.Field; -import java.net.URL; -import java.net.URLClassLoader; import java.util.*; @@ -15,7 +13,7 @@ import com.microsoft.azure.functions.worker.description.*; import com.microsoft.azure.functions.worker.reflect.*; -public class JavaMethodExecutorTest { +public class FunctionDefinitionTest { private static FunctionClassLoaderProvider functionClassLoaderProvider = new FunctionClassLoaderProvider(); @@ -24,34 +22,33 @@ public static void setClassLoaderProvider() throws Exception { Field classLoaderInstance = FunctionClassLoaderProvider.class.getDeclaredField("classLoaderInstance"); classLoaderInstance.setAccessible(true); classLoaderInstance.set(null, null); - String targetPath = JavaMethodExecutorTest.class.getProtectionDomain().getCodeSource().getLocation().getPath(); + String targetPath = FunctionDefinitionTest.class.getProtectionDomain().getCodeSource().getLocation().getPath(); File testJar = new File(targetPath + "/TestFunctionsClass.jar"); functionClassLoaderProvider.addCustomerUrl(testJar.toURI().toURL()); } @Test - public void functionMethodLoadSucceeds() throws Exception { + public void functionDefinition_one_method_buildSucceeds() throws Exception { FunctionMethodDescriptor descriptor = new FunctionMethodDescriptor("testid", "TestHttpTrigger","com.microsoft.azure.functions.worker.broker.tests.TestFunctionsClass.TestHttpTrigger","TestFunctionsClass.jar"); Map bindings = new HashMap<>(); bindings.put("$return", BindingInfo.newBuilder().setDirection(BindingInfo.Direction.out).build()); - JavaMethodExecutor executor = new FunctionMethodExecutorImpl(descriptor, bindings, functionClassLoaderProvider); - assertTrue(executor.getOverloadResolver().hasCandidates()); - assertFalse(executor.getOverloadResolver().hasMultipleCandidates()); + FunctionDefinition functionDefinition = new FunctionDefinition(descriptor, bindings, functionClassLoaderProvider); + assertNotNull(functionDefinition.getCandidate()); } @Test(expected = NoSuchMethodException.class) - public void functionMethodLoadFails_DoesnotExist() throws Exception { + public void functionDefinition_no_method_DoesnotExist() throws Exception { FunctionMethodDescriptor descriptor = new FunctionMethodDescriptor("testid", "TestHttpTrigger1","com.microsoft.azure.functions.worker.broker.tests.TestFunctionsClass.TestHttpTrigger1","TestFunctionsClass.jar"); Map bindings = new HashMap<>(); bindings.put("$return", BindingInfo.newBuilder().setDirection(BindingInfo.Direction.out).build()); - JavaMethodExecutor executor = new FunctionMethodExecutorImpl(descriptor, bindings, functionClassLoaderProvider); + FunctionDefinition functionDefinition = new FunctionDefinition(descriptor, bindings, functionClassLoaderProvider); } @Test(expected = UnsupportedOperationException.class) - public void functionMethodLoadFails_DuplicateAnnotations() throws Exception { + public void functionDefinition_multiple_methods_DuplicateAnnotations() throws Exception { FunctionMethodDescriptor descriptor = new FunctionMethodDescriptor("testid", "TestHttpTriggerOverload","com.microsoft.azure.functions.worker.broker.tests.TestFunctionsClass.TestHttpTriggerOverload","TestFunctionsClass.jar"); Map bindings = new HashMap<>(); bindings.put("$return", BindingInfo.newBuilder().setDirection(BindingInfo.Direction.out).build()); - JavaMethodExecutor executor = new FunctionMethodExecutorImpl(descriptor, bindings, functionClassLoaderProvider); + FunctionDefinition functionDefinition = new FunctionDefinition(descriptor, bindings, functionClassLoaderProvider); } }