Repository: Azure/azure-functions-java-worker
Pull request #652 (2022-09-19 17:20:10)

refactor java worker - add middleware support
<!-- Please provide all the information below.  -->

### Issue describing the changes in this PR

This PR include two features:

## 1st feature
mainly decoupled function definition (build at function load request processing) and function executor (build at invocation request process). It includes updates as below

- The worker used to build function executor at `FunctionLoadRequest`, now updated the logic to build the executor at `InvocationRequest`. 
- Update the executor only depends on the execution context passed to it at every invocation request, make the execution logic more clear. 
- Making `ExecutionContextDataSource` class to `public`, as this class is only used internal in java worker, it should cause any issue on changing it's scope to `public`. 
- Update `ExecutionContextDataSource` to use builder pattern as it has to many arguments in old constructor. 
- Adding three fields `dataStore`, `methodBindInfo`, `containingClass` to `ExecutionContextDataSource`, since decoupled the function definition and function executor, now function executor relies on  `ExecutionContextDataSource` to get all required objects. 
- Update `MethodBindInfo`, `ParamBindInfo` from nested class to top class as they are used by other class besides their parent class.

## 2nd feature
Add support for middleware.
This PR along with [core library PR #11](https://github.com/Azure/azure-functions-java-core-library/pull/11) and [durable java sdk PR #87](https://github.com/microsoft/durabletask-java/pull/87)  resolve https://github.com/Azure/azure-functions-java-worker/issues/595

### Pull request checklist

* [X] My changes **do not** require documentation changes
  * [ ] Otherwise: Documentation issue linked to PR
* [X] My changes **should not** be added to the release notes for the next release
  * [ ] Otherwise: I've added my notes to `release_notes.md`
* [X] My changes **do not** need to be backported to a previous version
  * [ ] Otherwise: Backport tracked by issue/PR #issue_or_pr
* [ ] I have added all required tests (Unit tests, E2E tests) -- to be done. 

<!-- Optional: delete if not applicable  -->
### Additional information

Additional PR information

kaibocai: /azp run

azure-pipelines[bot]: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
kaibocai: rebase the dev branch

Patch:
diff --git a/pom.xml b/pom.xml
index 6b829ec..bf3bdb9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,7 +20,7 @@
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <maven.compiler.source>1.8</maven.compiler.source>
     <maven.compiler.target>1.8</maven.compiler.target>
-    <azure.functions.java.core.library.version>1.0.0</azure.functions.java.core.library.version>
+    <azure.functions.java.core.library.version>1.1.0</azure.functions.java.core.library.version>
     <azure.functions.java.library.version>2.0.1</azure.functions.java.library.version>
   </properties>
   <licenses>
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 36cbd49..3ab1637 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,23 +47,24 @@ public final class BindingDataStore {
         }
     }
 
-    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<BindingData> getDataByName(String name, Type target) {
-    	return this.inputSources.get(name).computeByName(name, target);
+        DataSource<?> parameterDataSource = this.inputSources.get(name);
+        if (parameterDataSource == null) {
+            throw new RuntimeException("Cannot find matched parameter name of customer function, please check if customer function is defined correctly");
+        }
+    	return parameterDataSource.computeByName(name, target);
     }
 
     public Optional<BindingData> getTriggerMetatDataByName(String name, Type target) {
-    	return this.metadataSources.get(name).computeByName(name, target);
+        DataSource<?> metadataDataSource = this.metadataSources.get(name);
+        if (metadataDataSource == null) {
+            throw new RuntimeException("Cannot find matched @BindingName of customer function, please check if customer function is defined correctly");
+        }
+    	return metadataDataSource.computeByName(name, target);
     }
 
     public Optional<BindingData> getDataByType(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 5fce958..207cbd0 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,20 +1,74 @@
 package com.microsoft.azure.functions.worker.binding;
 
+import com.microsoft.azure.functions.*;
+import com.microsoft.azure.functions.internal.spi.middleware.MiddlewareContext;
+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;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Parameter;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
 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;
+public final class ExecutionContextDataSource extends DataSource<ExecutionContext> implements MiddlewareContext {
+    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;
+
+    /*
+    Key is the name defined on customer function parameters. For ex:
+    @HttpTrigger(
+            name = "req",
+            methods = {HttpMethod.GET, HttpMethod.POST},
+            authLevel = AuthorizationLevel.ANONYMOUS)
+    HttpRequestMessage<Optional<String>> request,
+    Here name will be the "req".
+
+    Value is java.lang.reflect.Parameter type
+     */
+    private final Map<String, Parameter> parameterDefinitions;
+
+    // currently the values are only Strings (resolved from grpc String values)
+    // but planning to support other types in the future
+    private final Map<String, Object> parameterValues;
+
+    // these are parameters provided by the middleware, which will override the host provided parameter values
+    // currently the values are only Strings, but planning to support other types in the future
+    private final Map<String, Object> middlewareParameterValues = new HashMap<>();
+    private Object returnValue;
+
+    //TODO: refactor class to have subclass dedicate to middleware to make logics clean
+    private static final DataOperations<ExecutionContext, Object> EXECONTEXT_DATA_OPERATIONS = new DataOperations<>();
+    static {
+        EXECONTEXT_DATA_OPERATIONS.addGenericOperation(ExecutionContext.class, DataOperations::generalAssignment);
+    }
 
-final class ExecutionContextDataSource extends DataSource<ExecutionContext> implements ExecutionContext {
-    ExecutionContextDataSource(String invocationId, String funcname, TraceContext traceContext, RetryContext retryContext) {
+    public ExecutionContextDataSource(String invocationId, TraceContext traceContext, RetryContext retryContext,
+                                      String funcname, BindingDataStore dataStore, MethodBindInfo methodBindInfo,
+                                      Class<?> containingClass, List<ParameterBinding> parameterBindings){
         super(null, null, EXECONTEXT_DATA_OPERATIONS);
         this.invocationId = invocationId;
         this.traceContext = traceContext;
         this.retryContext = retryContext;
         this.logger = WorkerLogManager.getInvocationLogger(invocationId);
         this.funcname = funcname;
+        this.dataStore = dataStore;
+        this.methodBindInfo = methodBindInfo;
+        this.containingClass = containingClass;
+        this.parameterDefinitions = getParameterDefinitions(methodBindInfo);
+        this.parameterValues = resolveParameterValuesForMiddleware(parameterBindings);
         this.setValue(this);
     }
 
@@ -32,15 +86,90 @@ final class ExecutionContextDataSource extends DataSource<ExecutionContext> 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<ExecutionContext, Object> 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;
+    }
+
+    private static Map<String, Parameter> getParameterDefinitions(MethodBindInfo methodBindInfo){
+        Map<String, Parameter> map = new HashMap<>();
+        for (ParamBindInfo paramBindInfo : methodBindInfo.getParams()) {
+            map.put(paramBindInfo.getName(), paramBindInfo.getParameter());
+        }
+        return map;
+    }
+
+
+    //TODO: leverage stream to do the check
+    @Override
+    public String getParameterName(String annotationSimpleClassName){
+        for (Map.Entry<String, Parameter> entry : this.parameterDefinitions.entrySet()){
+            if (hasAnnotation(entry.getValue(), annotationSimpleClassName)){
+                return entry.getKey();
+            }
+        }
+        return null;
+    }
+
+    private static boolean hasAnnotation(Parameter parameter, String annotationSimpleClassName){
+        Annotation[] annotations = parameter.getAnnotations();
+        for (Annotation annotation : annotations) {
+            if(annotation.annotationType().getSimpleName().equals(annotationSimpleClassName)){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // TODO: Refactor the code in V5 to make resolve arguments logics before middleware invocation.
+    // for now only supporting String parameter values mapped to String values
+    private static Map<String, Object> resolveParameterValuesForMiddleware(List<ParameterBinding> parameterBindings){
+        Map<String, Object> map = new HashMap<>();
+        for (ParameterBinding parameterBinding : parameterBindings) {
+            TypedData typedData = parameterBinding.getData();
+            if (typedData.getDataCase() == TypedData.DataCase.STRING){
+                map.put(parameterBinding.getName(), typedData.getString());
+            }
+        }
+        return map;
+    }
+
+    @Override
+    public Object getParameterValue(String name) {
+        return this.parameterValues.get(name);
+    }
+
+    @Override
+    public void updateParameterValue(String name, Object value) {
+        this.middlewareParameterValues.put(name, value);
+    }
+
+    @Override
+    public Object getReturnValue() {
+        return this.returnValue;
+    }
+
+    @Override
+    public void updateReturnValue(Object returnValue) {
+        this.returnValue = returnValue;
+        // set the return value that will be sent back to host
+        this.dataStore.setDataTargetValue(BindingDataStore.RETURN_NAME, this.returnValue);
+    }
+
+    public Optional<BindingData> getBindingData(String paramName, Type paramType) {
+        Object inputValue = this.middlewareParameterValues.get(paramName);
+        if (inputValue != null) {
+            return Optional.of(new BindingData(inputValue));
+        }else{
+            return dataStore.getDataByName(paramName, paramType);
+        }
+    }
 }
\ 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 f033937..73a283a 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 @@ public class CoreTypeResolver {
 		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/EnhancedJavaMethodExecutorImpl.java b/src/main/java/com/microsoft/azure/functions/worker/broker/EnhancedJavaMethodExecutorImpl.java
index 2a6a031..5d860d1 100644
--- a/src/main/java/com/microsoft/azure/functions/worker/broker/EnhancedJavaMethodExecutorImpl.java
+++ b/src/main/java/com/microsoft/azure/functions/worker/broker/EnhancedJavaMethodExecutorImpl.java
@@ -1,70 +1,28 @@
 package com.microsoft.azure.functions.worker.broker;
 
-import java.lang.reflect.*;
-import java.util.*;
-
 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.*;
 
 /**
  * Used to executor of arbitrary Java method in any JAR using reflection.
  * Thread-Safety: Multiple thread.
  */
 public class EnhancedJavaMethodExecutorImpl implements JavaMethodExecutor {
-    public EnhancedJavaMethodExecutorImpl(FunctionMethodDescriptor descriptor, Map<String, BindingInfo> 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<String, BindingInfo> entry : bindingInfos.entrySet()) {
-            this.bindingDefinitions.put(entry.getKey(), new BindingDefinition(entry.getKey(), entry.getValue()));
-        }
+    public EnhancedJavaMethodExecutorImpl(ClassLoader classLoader) {
+        this.classLoader = classLoader;
     }
 
-    public Map<String, BindingDefinition> 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 = ParameterResolver.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.updateReturnValue(retValue);
         } finally {
             Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
         }
     }
-
-    private Class<?> getContainingClass(String className) throws ClassNotFoundException {
-        return Class.forName(className, false, this.classLoader);
-    }
-
-    private final Class<?> containingClass;
-    private final ClassLoader classLoader;
-    private final ParameterResolver overloadResolver;
-    private final Map<String, BindingDefinition> bindingDefinitions;
 }
diff --git a/src/main/java/com/microsoft/azure/functions/worker/broker/FactoryJavaMethodExecutor.java b/src/main/java/com/microsoft/azure/functions/worker/broker/FactoryJavaMethodExecutor.java
deleted file mode 100644
index f2c4bab..0000000
--- a/src/main/java/com/microsoft/azure/functions/worker/broker/FactoryJavaMethodExecutor.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.microsoft.azure.functions.worker.broker;
-
-import com.microsoft.azure.functions.rpc.messages.BindingInfo;
-import com.microsoft.azure.functions.worker.WorkerLogManager;
-import com.microsoft.azure.functions.worker.description.FunctionMethodDescriptor;
-import com.microsoft.azure.functions.worker.reflect.ClassLoaderProvider;
-import org.apache.commons.lang3.SystemUtils;
-
-import java.net.MalformedURLException;
-import java.util.Map;
-
-public class FactoryJavaMethodExecutor {
-    public JavaMethodExecutor getJavaMethodExecutor(FunctionMethodDescriptor descriptor, Map<String, BindingInfo> bindings, ClassLoaderProvider classLoaderProvider)
-            throws MalformedURLException, ClassNotFoundException, NoSuchMethodException {
-        if(SystemUtils.IS_JAVA_1_8) {
-            WorkerLogManager.getSystemLogger().info("Loading JavaMethodExecutorImpl");
-            return new JavaMethodExecutorImpl(descriptor, bindings, classLoaderProvider);
-        } else {
-            WorkerLogManager.getSystemLogger().info("Loading EnhancedJavaMethodExecutorImpl");
-            return new EnhancedJavaMethodExecutorImpl(descriptor, bindings, classLoaderProvider);
-        }
-    }
-}
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 0000000..eb42c20
--- /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 MethodBindInfo candidate;
+    private final Map<String, BindingDefinition> bindingDefinitions;
+
+    public FunctionDefinition(FunctionMethodDescriptor descriptor, Map<String, BindingInfo> 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<String, BindingInfo> 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<MethodBindInfo> 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<String, BindingDefinition> getBindingDefinitions() {
+        return bindingDefinitions;
+    }
+
+    public MethodBindInfo getCandidate() {
+        return candidate;
+    }
+}
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 7a10ac1..fc1331b 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
@@ -1,22 +1,25 @@
 package com.microsoft.azure.functions.worker.broker;
 
 import java.io.File;
-import java.io.FileFilter;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.net.URL;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
 
+import com.microsoft.azure.functions.internal.spi.middleware.Middleware;
 import com.microsoft.azure.functions.rpc.messages.*;
 import com.microsoft.azure.functions.worker.Constants;
 import com.microsoft.azure.functions.worker.WorkerLogManager;
 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.chain.FunctionExecutionMiddleware;
+import com.microsoft.azure.functions.worker.chain.InvocationChainFactory;
 import com.microsoft.azure.functions.worker.description.FunctionMethodDescriptor;
 import com.microsoft.azure.functions.worker.reflect.ClassLoaderProvider;
-
 import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.commons.lang3.tuple.ImmutablePair;
 
@@ -25,6 +28,13 @@ import org.apache.commons.lang3.tuple.ImmutablePair;
  * reflection, and invoke them at runtime. Thread-Safety: Multiple thread.
  */
 public class JavaFunctionBroker {
+
+	//TODO: build dedicate ImmutablePair class with meaningful fields.
+	private final Map<String, ImmutablePair<String, FunctionDefinition>> methods;
+	private final ClassLoaderProvider classLoaderProvider;
+	private String workerDirectory;
+	private final AtomicBoolean invocationChainFactoryInitialized = new AtomicBoolean(false);
+	private volatile InvocationChainFactory invocationChainFactory;
 	public JavaFunctionBroker(ClassLoaderProvider classLoaderProvider) {
 		this.methods = new ConcurrentHashMap<>();
 		this.classLoaderProvider = classLoaderProvider;
@@ -33,34 +43,65 @@ public class JavaFunctionBroker {
 	public void loadMethod(FunctionMethodDescriptor descriptor, Map<String, BindingInfo> bindings)
 			throws ClassNotFoundException, NoSuchMethodException, IOException {
 		descriptor.validate();
-
 		addSearchPathsToClassLoader(descriptor);
-		JavaMethodExecutor executor = new FactoryJavaMethodExecutor().getJavaMethodExecutor(descriptor, bindings, classLoaderProvider);
+		initializeInvocationChainFactory();
+		FunctionDefinition functionDefinition = new FunctionDefinition(descriptor, bindings, classLoaderProvider);
+		this.methods.put(descriptor.getId(), new ImmutablePair<>(descriptor.getName(), functionDefinition));
+	}
 
-		this.methods.put(descriptor.getId(), new ImmutablePair<>(descriptor.getName(), executor));
+	private void initializeInvocationChainFactory() {
+		if (!invocationChainFactoryInitialized.getAndSet(true)) {
+			ArrayList<Middleware> middlewares = new ArrayList<>();
+			try {
+				//ServiceLoader will use thread context classloader to verify loaded class
+				Thread.currentThread().setContextClassLoader(classLoaderProvider.createClassLoader());
+				for (Middleware middleware : ServiceLoader.load(Middleware.class)) {
+					middlewares.add(middleware);
+					WorkerLogManager.getSystemLogger().info("Load middleware " + middleware.getClass().getSimpleName());
+				}
+			} finally {
+				Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
+			}
+			middlewares.add(getFunctionExecutionMiddleWare());
+			this.invocationChainFactory = new InvocationChainFactory(middlewares);
+		}
+	}
+
+	private FunctionExecutionMiddleware getFunctionExecutionMiddleWare() {
+		FunctionExecutionMiddleware functionExecutionMiddleware = new FunctionExecutionMiddleware(
+				JavaMethodExecutors.createJavaMethodExecutor(this.classLoaderProvider.createClassLoader()));
+		WorkerLogManager.getSystemLogger().info("Load last middleware: FunctionExecutionMiddleware");
+		return functionExecutionMiddleware;
 	}
 
 	public Optional<TypedData> invokeMethod(String id, InvocationRequest request, List<ParameterBinding> outputs)
 			throws Exception {
-		ImmutablePair<String, JavaMethodExecutor> methodEntry = this.methods.get(id);
-		JavaMethodExecutor executor = methodEntry.right;
-		if (executor == null) {
+		ExecutionContextDataSource executionContextDataSource = buildExecutionContext(id, request);
+		this.invocationChainFactory.create().doNext(executionContextDataSource);
+		outputs.addAll(executionContextDataSource.getDataStore().getOutputParameterBindings(true));
+		return executionContextDataSource.getDataStore().getDataTargetTypedValue(BindingDataStore.RETURN_NAME);
+	}
+
+	private ExecutionContextDataSource buildExecutionContext(String id,  InvocationRequest request)
+			throws NoSuchMethodException {
+		ImmutablePair<String, FunctionDefinition> 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);
+		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());
+		ExecutionContextDataSource executionContextDataSource = new ExecutionContextDataSource(request.getInvocationId(),
+				traceContext, retryContext, methodEntry.left, dataStore, functionDefinition.getCandidate(),
+				functionDefinition.getContainingClass(), request.getInputDataList());
+		dataStore.addExecutionContextSource(executionContextDataSource);
+		return executionContextDataSource;
 	}
 
 	public Optional<String> getMethodName(String id) {
@@ -148,27 +189,7 @@ public class JavaFunctionBroker {
 		}
 	}
 
-	void verifyLibrariesExist (File workerLib, String workerLibPath) throws FileNotFoundException{
-		if(!workerLib.exists()) {
-			throw new FileNotFoundException("Error loading worker jars, from path:  " + workerLibPath);
-		} else {
-			File[] jarFiles = workerLib.listFiles(new FileFilter() {
-				@Override
-				public boolean accept(File file) {
-					return file.isFile() && file.getName().endsWith(".jar");
-				}
-			});
-			if(jarFiles.length == 0) {
-				throw new FileNotFoundException("Error loading worker jars, from path:  " + workerLibPath + ". Jars size is zero");
-			}
-		}
-	}
-
 	public void setWorkerDirectory(String workerDirectory) {
 		this.workerDirectory = workerDirectory;
 	}
-
-	private final Map<String, ImmutablePair<String, JavaMethodExecutor>> 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 4928cad..7cd16ce 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 @@ import com.microsoft.azure.functions.worker.binding.*;
  * Thread-Safety: Multiple thread.
  */
 public interface JavaMethodExecutor {
-    Map<String, BindingDefinition> 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/JavaMethodExecutorImpl.java b/src/main/java/com/microsoft/azure/functions/worker/broker/JavaMethodExecutorImpl.java
index bad1d2e..42ffdd1 100644
--- a/src/main/java/com/microsoft/azure/functions/worker/broker/JavaMethodExecutorImpl.java
+++ b/src/main/java/com/microsoft/azure/functions/worker/broker/JavaMethodExecutorImpl.java
@@ -1,65 +1,25 @@
 package com.microsoft.azure.functions.worker.broker;
 
-import java.lang.reflect.*;
-import java.net.*;
-import java.util.*;
-
 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.*;
 
 /**
  * Used to executor of arbitrary Java method in any JAR using reflection.
  * Thread-Safety: Multiple thread.
  */
 public class JavaMethodExecutorImpl implements JavaMethodExecutor {
-    public JavaMethodExecutorImpl(FunctionMethodDescriptor descriptor, Map<String, BindingInfo> bindingInfos, ClassLoaderProvider classLoaderProvider)
-            throws MalformedURLException, ClassNotFoundException, NoSuchMethodException
-    {
-        descriptor.validateMethodInfo();
-
-        this.containingClass = getContainingClass(descriptor.getFullClassName(), classLoaderProvider);
-        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 static final JavaMethodExecutorImpl INSTANCE = new JavaMethodExecutorImpl();
 
-        for (Map.Entry<String, BindingInfo> entry : bindingInfos.entrySet()) {
-            this.bindingDefinitions.put(entry.getKey(), new BindingDefinition(entry.getKey(), entry.getValue()));
-        }
+    public static JavaMethodExecutorImpl getInstance(){
+        return INSTANCE;
     }
 
-    public Map<String, BindingDefinition> getBindingDefinitions() { return this.bindingDefinitions; }
+    private JavaMethodExecutorImpl () {}
 
-    public ParameterResolver getOverloadResolver() { return this.overloadResolver; }
-
-    public void execute(BindingDataStore dataStore) throws Exception {
-        Object retValue = this.overloadResolver.resolve(dataStore)
+    public void execute(ExecutionContextDataSource executionContextDataSource) throws Exception {
+        Object retValue = ParameterResolver.resolveArguments(executionContextDataSource)
                 .orElseThrow(() -> new NoSuchMethodException("Cannot locate the method signature with the given input"))
-                .invoke(() -> this.containingClass.newInstance());
-        dataStore.setDataTargetValue(BindingDataStore.RETURN_NAME, retValue);
-    }
-
-    private Class<?> getContainingClass(String className, ClassLoaderProvider classLoaderProvider) throws ClassNotFoundException {
-        ClassLoader classLoader = classLoaderProvider.createClassLoader();
-        return Class.forName(className, true, classLoader);
+                .invoke(() -> executionContextDataSource.getContainingClass().newInstance());
+        executionContextDataSource.updateReturnValue(retValue);
     }
-
-    private Class<?> containingClass;
-    private final ParameterResolver overloadResolver;
-    private final Map<String, BindingDefinition> bindingDefinitions;
 }
diff --git a/src/main/java/com/microsoft/azure/functions/worker/broker/JavaMethodExecutors.java b/src/main/java/com/microsoft/azure/functions/worker/broker/JavaMethodExecutors.java
new file mode 100644
index 0000000..309ad7b
--- /dev/null
+++ b/src/main/java/com/microsoft/azure/functions/worker/broker/JavaMethodExecutors.java
@@ -0,0 +1,16 @@
+package com.microsoft.azure.functions.worker.broker;
+
+import com.microsoft.azure.functions.worker.WorkerLogManager;
+import org.apache.commons.lang3.SystemUtils;
+
+public class JavaMethodExecutors {
+    public static JavaMethodExecutor createJavaMethodExecutor(ClassLoader classLoader) {
+        if(SystemUtils.IS_JAVA_1_8) {
+            WorkerLogManager.getSystemLogger().info("Loading JavaMethodExecutorImpl");
+            return JavaMethodExecutorImpl.getInstance();
+        } else {
+            WorkerLogManager.getSystemLogger().info("Loading EnhancedJavaMethodExecutorImpl");
+            return new EnhancedJavaMethodExecutorImpl(classLoader);
+        }
+    }
+}
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 0000000..ba8258f
--- /dev/null
+++ b/src/main/java/com/microsoft/azure/functions/worker/broker/MethodBindInfo.java
@@ -0,0 +1,36 @@
+package com.microsoft.azure.functions.worker.broker;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public final class MethodBindInfo {
+
+    private final Method method;
+    private final List<ParamBindInfo> params;
+    private final boolean hasImplicitOutput;
+    MethodBindInfo(Method method) {
+        this.method = method;
+        this.params = Arrays.stream(method.getParameters()).map(ParamBindInfo::new).collect(Collectors.toList());
+        this.hasImplicitOutput = checkImplicitOutput(params);
+    }
+
+    private static boolean checkImplicitOutput(List<ParamBindInfo> params){
+        return params.stream().anyMatch(ParamBindInfo::isImplicitOutput);
+    }
+
+    public Method getMethod() {
+        return method;
+    }
+
+    public List<ParamBindInfo> getParams() {
+        return params;
+    }
+
+    public boolean hasImplicitOutput() {
+        return hasImplicitOutput;
+    }
+}
+
+
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 0000000..a0c2d12
--- /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
index df7d4bd..5048420 100644
--- a/src/main/java/com/microsoft/azure/functions/worker/broker/ParameterResolver.java
+++ b/src/main/java/com/microsoft/azure/functions/worker/broker/ParameterResolver.java
@@ -1,15 +1,11 @@
 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 com.microsoft.azure.functions.worker.binding.ExecutionContextDataSource;
 import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.commons.lang3.reflect.TypeUtils;
 
@@ -22,98 +18,56 @@ import com.microsoft.azure.functions.worker.binding.BindingDataStore;
  * 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<JavaMethodInvokeInfo> resolve(BindingDataStore dataStore) {
-        InvokeInfoBuilder invoker = this.resolve(this.candidates.get(0), dataStore);
+    public static Optional<JavaMethodInvokeInfo> resolveArguments(ExecutionContextDataSource executionContextDataSource) {
+        InvokeInfoBuilder invoker = resolve(executionContextDataSource);
         if (invoker != null) {
-            dataStore.promoteDataTargets(invoker.outputsId);
+            executionContextDataSource.getDataStore().promoteDataTargets(invoker.getOutputsId());
             return Optional.of(invoker.build());
         }
         return Optional.empty();
     }
 
-    private InvokeInfoBuilder resolve(MethodBindInfo method, BindingDataStore dataStore) {
+    private static InvokeInfoBuilder resolve(ExecutionContextDataSource executionContextDataSource) {
         try {
+            MethodBindInfo method = executionContextDataSource.getMethodBindInfo();
+            BindingDataStore dataStore = executionContextDataSource.getDataStore();
             final InvokeInfoBuilder invokeInfo = new InvokeInfoBuilder(method);
-            for (ParamBindInfo param : method.params) {
-                Optional<BindingData> 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);
+            for (ParamBindInfo param : method.getParams()) {
+                String paramName = param.getName();
+                Type paramType = param.getType();
+                String paramBindingNameAnnotation = param.getBindingNameAnnotation();
+                Optional<BindingData> argument;
+                if (OutputBinding.class.isAssignableFrom(TypeUtils.getRawType(paramType, null))) {
+                    argument = dataStore.getOrAddDataTarget(invokeInfo.getOutputsId(), paramName, paramType, false);
+                }
+                else if (paramName != null && !paramName.isEmpty()) {
+                    argument = executionContextDataSource.getBindingData(paramName, paramType);
+                }
+                else if (paramName == null && !paramBindingNameAnnotation.isEmpty()) {
+                    argument = dataStore.getTriggerMetatDataByName(paramBindingNameAnnotation, paramType);
                 }
                 else {
-                	argument = dataStore.getDataByType(param.type);
+                    argument = dataStore.getDataByType(paramType);
                 }
                 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);
+            if (!method.getMethod().getReturnType().equals(void.class) && !method.getMethod().getReturnType().equals(Void.class)) {
+                dataStore.getOrAddDataTarget(invokeInfo.getOutputsId(), BindingDataStore.RETURN_NAME, method.getMethod().getReturnType(), method.hasImplicitOutput());
             }
             return invokeInfo;
         } catch (Exception ex) {
-        	ExceptionUtils.rethrow(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();
-        }
+    public static final class InvokeInfoBuilder extends JavaMethodInvokeInfo.Builder {
+        public InvokeInfoBuilder(MethodBindInfo method) { super.setMethod(method.getMethod()); }
+        private final UUID outputsId = UUID.randomUUID();
 
-        private boolean checkHasImplicitOutput(){
-            for (ParamBindInfo paramBindInfo : this.params){
-                if (paramBindInfo.hasImplicitOutput) return true;
-            }
-            return false;
+        public UUID getOutputsId() {
+            return outputsId;
         }
-
-        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<MethodBindInfo> candidates;
 }
diff --git a/src/main/java/com/microsoft/azure/functions/worker/chain/FunctionExecutionMiddleware.java b/src/main/java/com/microsoft/azure/functions/worker/chain/FunctionExecutionMiddleware.java
new file mode 100644
index 0000000..6886876
--- /dev/null
+++ b/src/main/java/com/microsoft/azure/functions/worker/chain/FunctionExecutionMiddleware.java
@@ -0,0 +1,21 @@
+package com.microsoft.azure.functions.worker.chain;
+
+import com.microsoft.azure.functions.internal.spi.middleware.Middleware;
+import com.microsoft.azure.functions.internal.spi.middleware.MiddlewareChain;
+import com.microsoft.azure.functions.internal.spi.middleware.MiddlewareContext;
+import com.microsoft.azure.functions.worker.binding.ExecutionContextDataSource;
+import com.microsoft.azure.functions.worker.broker.JavaMethodExecutor;
+
+public class FunctionExecutionMiddleware implements Middleware {
+
+    private final JavaMethodExecutor functionMethodExecutor;
+
+    public FunctionExecutionMiddleware(JavaMethodExecutor functionExecutionMiddleware) {
+        this.functionMethodExecutor = functionExecutionMiddleware;
+    }
+
+    @Override
+    public void invoke(MiddlewareContext context, MiddlewareChain chain) throws Exception{
+        this.functionMethodExecutor.execute((ExecutionContextDataSource) context);
+    }
+}
diff --git a/src/main/java/com/microsoft/azure/functions/worker/chain/InvocationChain.java b/src/main/java/com/microsoft/azure/functions/worker/chain/InvocationChain.java
new file mode 100644
index 0000000..a6c9ce3
--- /dev/null
+++ b/src/main/java/com/microsoft/azure/functions/worker/chain/InvocationChain.java
@@ -0,0 +1,24 @@
+package com.microsoft.azure.functions.worker.chain;
+
+
+import com.microsoft.azure.functions.internal.spi.middleware.Middleware;
+import com.microsoft.azure.functions.internal.spi.middleware.MiddlewareChain;
+import com.microsoft.azure.functions.internal.spi.middleware.MiddlewareContext;
+
+import java.util.Iterator;
+import java.util.List;
+
+public class InvocationChain implements MiddlewareChain {
+     private final Iterator<Middleware> middlewareIterator;
+
+     public InvocationChain(List<Middleware> middlewares){
+         this.middlewareIterator = middlewares.iterator();
+     }
+
+    @Override
+    public void doNext(MiddlewareContext context) throws Exception {
+        if (middlewareIterator.hasNext()) {
+            middlewareIterator.next().invoke(context, this);
+        }
+    }
+}
diff --git a/src/main/java/com/microsoft/azure/functions/worker/chain/InvocationChainFactory.java b/src/main/java/com/microsoft/azure/functions/worker/chain/InvocationChainFactory.java
new file mode 100644
index 0000000..3a0c23d
--- /dev/null
+++ b/src/main/java/com/microsoft/azure/functions/worker/chain/InvocationChainFactory.java
@@ -0,0 +1,20 @@
+package com.microsoft.azure.functions.worker.chain;
+
+
+import com.microsoft.azure.functions.internal.spi.middleware.Middleware;
+import com.microsoft.azure.functions.internal.spi.middleware.MiddlewareChain;
+
+import java.util.List;
+
+public class InvocationChainFactory {
+
+    private final List<Middleware> middlewares;
+
+    public InvocationChainFactory(List<Middleware> middlewares) {
+        this.middlewares = middlewares;
+    }
+
+    public MiddlewareChain create(){
+        return new InvocationChain(middlewares);
+    }
+}


Test patch:
diff --git a/src/test/java/com/microsoft/azure/functions/worker/broker/JavaFunctionBrokerTest.java b/src/test/java/com/microsoft/azure/functions/worker/broker/JavaFunctionBrokerTest.java
index 231c1d6..7280de5 100644
--- a/src/test/java/com/microsoft/azure/functions/worker/broker/JavaFunctionBrokerTest.java
+++ b/src/test/java/com/microsoft/azure/functions/worker/broker/JavaFunctionBrokerTest.java
@@ -102,18 +102,4 @@ public class JavaFunctionBrokerTest {
         // In case of non-http request, it will not modify the triggerMetadata
         assertEquals(expectedCount, actualTriggerMetadata.size());
     }
-
-    @Test(expected = FileNotFoundException.class)
-    public void checkLibFolderNoWorkerLib() throws Exception {
-        JavaFunctionBroker broker = new JavaFunctionBroker(null);
-        broker.verifyLibrariesExist (new File(""), null);
-    }
-
-    @Test(expected = FileNotFoundException.class)
-    public void checkLibFolderNoJarsInLib() throws Exception {
-        JavaFunctionBroker broker = new JavaFunctionBroker(null);
-        String path = "../";
-        File file = new File(path);
-        broker.verifyLibrariesExist (file, path);
-    }
 }
diff --git a/src/test/java/com/microsoft/azure/functions/worker/broker/tests/JavaMethodExecutorTests.java b/src/test/java/com/microsoft/azure/functions/worker/broker/tests/FunctionDefinitionTests.java
similarity index 73%
rename from src/test/java/com/microsoft/azure/functions/worker/broker/tests/JavaMethodExecutorTests.java
rename to src/test/java/com/microsoft/azure/functions/worker/broker/tests/FunctionDefinitionTests.java
index c1bad3a..7cdbe6f 100644
--- a/src/test/java/com/microsoft/azure/functions/worker/broker/tests/JavaMethodExecutorTests.java
+++ b/src/test/java/com/microsoft/azure/functions/worker/broker/tests/FunctionDefinitionTests.java
@@ -1,26 +1,23 @@
 package com.microsoft.azure.functions.worker.broker.tests;
 
 import org.junit.*;
-
-import static junit.framework.TestCase.*;
 import java.util.*;
-
-
 import com.microsoft.azure.functions.rpc.messages.*;
 import com.microsoft.azure.functions.worker.broker.*;
 import com.microsoft.azure.functions.worker.description.*;
 import com.microsoft.azure.functions.worker.reflect.*;
+import static junit.framework.TestCase.*;
+
 
-public class JavaMethodExecutorTests {
+public class FunctionDefinitionTests {
 	
 	@Test    
     public void functionMethodLoadSucceeds_8() throws Exception {
 		FunctionMethodDescriptor descriptor = new FunctionMethodDescriptor("testid", "TestHttpTrigger","com.microsoft.azure.functions.worker.broker.tests.TestFunctionsClass.TestHttpTrigger","TestFunctionsClass.jar");
 		Map<String, BindingInfo> bindings = new HashMap<>();
 		bindings.put("$return", BindingInfo.newBuilder().setDirection(BindingInfo.Direction.out).build());
-		JavaMethodExecutor executor = new FactoryJavaMethodExecutor().getJavaMethodExecutor(descriptor, bindings, new FactoryClassLoader().createClassLoaderProvider());
-		assertTrue(executor.getOverloadResolver().hasCandidates());
-		assertFalse(executor.getOverloadResolver().hasMultipleCandidates());
+		FunctionDefinition functionDefinition = new FunctionDefinition(descriptor, bindings, new FactoryClassLoader().createClassLoaderProvider());
+		assertNotNull(functionDefinition.getCandidate());
     }
 	
 	@Test(expected = NoSuchMethodException.class)   
@@ -28,7 +25,7 @@ public class JavaMethodExecutorTests {
 		FunctionMethodDescriptor descriptor = new FunctionMethodDescriptor("testid", "TestHttpTrigger1","com.microsoft.azure.functions.worker.broker.tests.TestFunctionsClass.TestHttpTrigger1","TestFunctionsClass.jar");
 		Map<String, BindingInfo> bindings = new HashMap<>();
 		bindings.put("$return", BindingInfo.newBuilder().setDirection(BindingInfo.Direction.out).build());
-		JavaMethodExecutor executor = new FactoryJavaMethodExecutor().getJavaMethodExecutor(descriptor, bindings, new FactoryClassLoader().createClassLoaderProvider());
+		FunctionDefinition functionDefinition = new FunctionDefinition(descriptor, bindings, new FactoryClassLoader().createClassLoaderProvider());
 		
     }
 	
@@ -37,7 +34,7 @@ public class JavaMethodExecutorTests {
 		FunctionMethodDescriptor descriptor = new FunctionMethodDescriptor("testid", "TestHttpTriggerOverload","com.microsoft.azure.functions.worker.broker.tests.TestFunctionsClass.TestHttpTriggerOverload","TestFunctionsClass.jar");
 		Map<String, BindingInfo> bindings = new HashMap<>();
 		bindings.put("$return", BindingInfo.newBuilder().setDirection(BindingInfo.Direction.out).build());
-		JavaMethodExecutor executor = new FactoryJavaMethodExecutor().getJavaMethodExecutor(descriptor, bindings, new FactoryClassLoader().createClassLoaderProvider());
+		FunctionDefinition functionDefinition = new FunctionDefinition(descriptor, bindings, new FactoryClassLoader().createClassLoaderProvider());
     }
 
 	@Test
@@ -46,9 +43,8 @@ public class JavaMethodExecutorTests {
 		Map<String, BindingInfo> bindings = new HashMap<>();
 		bindings.put("$return", BindingInfo.newBuilder().setDirection(BindingInfo.Direction.out).build());
 		System.setProperty("java.specification.version", "11");
-		JavaMethodExecutor executor = new FactoryJavaMethodExecutor().getJavaMethodExecutor(descriptor, bindings, new FactoryClassLoader().createClassLoaderProvider());
-		assertTrue(executor.getOverloadResolver().hasCandidates());
-		assertFalse(executor.getOverloadResolver().hasMultipleCandidates());
+		FunctionDefinition functionDefinition = new FunctionDefinition(descriptor, bindings, new FactoryClassLoader().createClassLoaderProvider());
+		assertNotNull(functionDefinition.getCandidate());
 	}
 
 	@Test(expected = NoSuchMethodException.class)
@@ -57,7 +53,7 @@ public class JavaMethodExecutorTests {
 		FunctionMethodDescriptor descriptor = new FunctionMethodDescriptor("testid", "TestHttpTrigger1","com.microsoft.azure.functions.worker.broker.tests.TestFunctionsClass.TestHttpTrigger1","TestFunctionsClass.jar");
 		Map<String, BindingInfo> bindings = new HashMap<>();
 		bindings.put("$return", BindingInfo.newBuilder().setDirection(BindingInfo.Direction.out).build());
-		JavaMethodExecutor executor = new FactoryJavaMethodExecutor().getJavaMethodExecutor(descriptor, bindings, new FactoryClassLoader().createClassLoaderProvider());
+		FunctionDefinition functionDefinition = new FunctionDefinition(descriptor, bindings, new FactoryClassLoader().createClassLoaderProvider());
 
 	}
 
@@ -67,6 +63,6 @@ public class JavaMethodExecutorTests {
 		FunctionMethodDescriptor descriptor = new FunctionMethodDescriptor("testid", "TestHttpTriggerOverload","com.microsoft.azure.functions.worker.broker.tests.TestFunctionsClass.TestHttpTriggerOverload","TestFunctionsClass.jar");
 		Map<String, BindingInfo> bindings = new HashMap<>();
 		bindings.put("$return", BindingInfo.newBuilder().setDirection(BindingInfo.Direction.out).build());
-		JavaMethodExecutor executor = new FactoryJavaMethodExecutor().getJavaMethodExecutor(descriptor, bindings, new FactoryClassLoader().createClassLoaderProvider());
+		FunctionDefinition functionDefinition = new FunctionDefinition(descriptor, bindings, new FactoryClassLoader().createClassLoaderProvider());
 	}
 }