Skip to content

Commit 9547726

Browse files
committed
middleware support logics (#659)
* middleware support logics * refactor code * rename middleware chain * refactor code * fix error * refactor code * minor updates * remove used local varaible * APIs change accordingly * update method name * update method name
1 parent cf983c3 commit 9547726

10 files changed

+230
-30
lines changed

src/main/java/com/microsoft/azure/functions/worker/binding/ExecutionContextDataSource.java

Lines changed: 115 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
package com.microsoft.azure.functions.worker.binding;
22

3-
import com.microsoft.azure.functions.ExecutionContext;
4-
import com.microsoft.azure.functions.RetryContext;
5-
import com.microsoft.azure.functions.TraceContext;
3+
import com.microsoft.azure.functions.*;
4+
import com.microsoft.azure.functions.internal.spi.middleware.MiddlewareContext;
5+
import com.microsoft.azure.functions.rpc.messages.ParameterBinding;
6+
import com.microsoft.azure.functions.rpc.messages.TypedData;
67
import com.microsoft.azure.functions.worker.WorkerLogManager;
78
import com.microsoft.azure.functions.worker.broker.MethodBindInfo;
8-
import java.util.logging.Logger;
9+
import com.microsoft.azure.functions.worker.broker.ParamBindInfo;
910

10-
public final class ExecutionContextDataSource extends DataSource<ExecutionContext> implements ExecutionContext {
11+
import java.lang.annotation.Annotation;
12+
import java.lang.reflect.Parameter;
13+
import java.lang.reflect.Type;
14+
import java.util.HashMap;
15+
import java.util.List;
16+
import java.util.Map;
17+
import java.util.Optional;
18+
import java.util.logging.Logger;
1119

20+
public final class ExecutionContextDataSource extends DataSource<ExecutionContext> implements MiddlewareContext {
1221
private final String invocationId;
1322
private final TraceContext traceContext;
1423
private final RetryContext retryContext;
@@ -18,14 +27,37 @@ public final class ExecutionContextDataSource extends DataSource<ExecutionContex
1827
private final MethodBindInfo methodBindInfo;
1928
private final Class<?> containingClass;
2029

30+
/*
31+
Key is the name defined on customer function parameters. For ex:
32+
@HttpTrigger(
33+
name = "req",
34+
methods = {HttpMethod.GET, HttpMethod.POST},
35+
authLevel = AuthorizationLevel.ANONYMOUS)
36+
HttpRequestMessage<Optional<String>> request,
37+
Here name will be the "req".
38+
39+
Value is java.lang.reflect.Parameter type
40+
*/
41+
private final Map<String, Parameter> parameterDefinitions;
42+
43+
// currently the values are only Strings (resolved from grpc String values)
44+
// but planning to support other types in the future
45+
private final Map<String, Object> parameterValues;
46+
47+
// these are parameters provided by the middleware, which will override the host provided parameter values
48+
// currently the values are only Strings, but planning to support other types in the future
49+
private final Map<String, Object> middlewareParameterValues = new HashMap<>();
50+
private Object returnValue;
51+
52+
//TODO: refactor class to have subclass dedicate to middleware to make logics clean
2153
private static final DataOperations<ExecutionContext, Object> EXECONTEXT_DATA_OPERATIONS = new DataOperations<>();
2254
static {
2355
EXECONTEXT_DATA_OPERATIONS.addGenericOperation(ExecutionContext.class, DataOperations::generalAssignment);
2456
}
2557

2658
public ExecutionContextDataSource(String invocationId, TraceContext traceContext, RetryContext retryContext,
27-
String funcname, BindingDataStore dataStore, MethodBindInfo methodBindInfo,
28-
Class<?> containingClass){
59+
String funcname, BindingDataStore dataStore, MethodBindInfo methodBindInfo,
60+
Class<?> containingClass, List<ParameterBinding> parameterBindings){
2961
super(null, null, EXECONTEXT_DATA_OPERATIONS);
3062
this.invocationId = invocationId;
3163
this.traceContext = traceContext;
@@ -35,6 +67,8 @@ public ExecutionContextDataSource(String invocationId, TraceContext traceContext
3567
this.dataStore = dataStore;
3668
this.methodBindInfo = methodBindInfo;
3769
this.containingClass = containingClass;
70+
this.parameterDefinitions = getParameterDefinitions(methodBindInfo);
71+
this.parameterValues = resolveParameterValuesForMiddleware(parameterBindings);
3872
this.setValue(this);
3973
}
4074

@@ -64,4 +98,78 @@ public MethodBindInfo getMethodBindInfo() {
6498
public Class<?> getContainingClass() {
6599
return containingClass;
66100
}
101+
102+
private static Map<String, Parameter> getParameterDefinitions(MethodBindInfo methodBindInfo){
103+
Map<String, Parameter> map = new HashMap<>();
104+
for (ParamBindInfo paramBindInfo : methodBindInfo.getParams()) {
105+
map.put(paramBindInfo.getName(), paramBindInfo.getParameter());
106+
}
107+
return map;
108+
}
109+
110+
111+
//TODO: leverage stream to do the check
112+
@Override
113+
public String getParameterName(String annotationSimpleClassName){
114+
for (Map.Entry<String, Parameter> entry : this.parameterDefinitions.entrySet()){
115+
if (hasAnnotation(entry.getValue(), annotationSimpleClassName)){
116+
return entry.getKey();
117+
}
118+
}
119+
return null;
120+
}
121+
122+
private static boolean hasAnnotation(Parameter parameter, String annotationSimpleClassName){
123+
Annotation[] annotations = parameter.getAnnotations();
124+
for (Annotation annotation : annotations) {
125+
if(annotation.annotationType().getSimpleName().equals(annotationSimpleClassName)){
126+
return true;
127+
}
128+
}
129+
return false;
130+
}
131+
132+
// TODO: Refactor the code in V5 to make resolve arguments logics before middleware invocation.
133+
// for now only supporting String parameter values mapped to String values
134+
private static Map<String, Object> resolveParameterValuesForMiddleware(List<ParameterBinding> parameterBindings){
135+
Map<String, Object> map = new HashMap<>();
136+
for (ParameterBinding parameterBinding : parameterBindings) {
137+
TypedData typedData = parameterBinding.getData();
138+
if (typedData.getDataCase() == TypedData.DataCase.STRING){
139+
map.put(parameterBinding.getName(), typedData.getString());
140+
}
141+
}
142+
return map;
143+
}
144+
145+
@Override
146+
public Object getParameterValue(String name) {
147+
return this.parameterValues.get(name);
148+
}
149+
150+
@Override
151+
public void updateParameterValue(String name, Object value) {
152+
this.middlewareParameterValues.put(name, value);
153+
}
154+
155+
@Override
156+
public Object getReturnValue() {
157+
return this.returnValue;
158+
}
159+
160+
@Override
161+
public void updateReturnValue(Object returnValue) {
162+
this.returnValue = returnValue;
163+
// set the return value that will be sent back to host
164+
this.dataStore.setDataTargetValue(BindingDataStore.RETURN_NAME, this.returnValue);
165+
}
166+
167+
public Optional<BindingData> getBindingData(String paramName, Type paramType) {
168+
Object inputValue = this.middlewareParameterValues.get(paramName);
169+
if (inputValue != null) {
170+
return Optional.of(new BindingData(inputValue));
171+
}else{
172+
return dataStore.getDataByName(paramName, paramType);
173+
}
174+
}
67175
}

src/main/java/com/microsoft/azure/functions/worker/broker/EnhancedJavaMethodExecutorImpl.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
package com.microsoft.azure.functions.worker.broker;
22

3-
import java.lang.invoke.WrongMethodTypeException;
4-
import java.lang.reflect.*;
5-
import java.util.*;
6-
7-
import com.microsoft.azure.functions.OutputBinding;
83
import com.microsoft.azure.functions.worker.binding.*;
9-
import org.apache.commons.lang3.exception.ExceptionUtils;
10-
import org.apache.commons.lang3.reflect.TypeUtils;
114

125
/**
136
* Used to executor of arbitrary Java method in any JAR using reflection.
@@ -27,7 +20,7 @@ public void execute(ExecutionContextDataSource executionContextDataSource) throw
2720
Object retValue = ParameterResolver.resolveArguments(executionContextDataSource)
2821
.orElseThrow(() -> new NoSuchMethodException("Cannot locate the method signature with the given input"))
2922
.invoke(() -> executionContextDataSource.getContainingClass().newInstance());
30-
executionContextDataSource.getDataStore().setDataTargetValue(BindingDataStore.RETURN_NAME, retValue);
23+
executionContextDataSource.updateReturnValue(retValue);
3124
} finally {
3225
Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
3326
}

src/main/java/com/microsoft/azure/functions/worker/broker/JavaFunctionBroker.java

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,20 @@
66
import java.net.URL;
77
import java.util.*;
88
import java.util.concurrent.ConcurrentHashMap;
9+
import java.util.concurrent.atomic.AtomicBoolean;
910

11+
import com.microsoft.azure.functions.internal.spi.middleware.Middleware;
1012
import com.microsoft.azure.functions.rpc.messages.*;
1113
import com.microsoft.azure.functions.worker.Constants;
14+
import com.microsoft.azure.functions.worker.WorkerLogManager;
1215
import com.microsoft.azure.functions.worker.binding.BindingDataStore;
1316
import com.microsoft.azure.functions.worker.binding.ExecutionContextDataSource;
1417
import com.microsoft.azure.functions.worker.binding.ExecutionRetryContext;
1518
import com.microsoft.azure.functions.worker.binding.ExecutionTraceContext;
19+
import com.microsoft.azure.functions.worker.chain.FunctionExecutionMiddleware;
20+
import com.microsoft.azure.functions.worker.chain.InvocationChainFactory;
1621
import com.microsoft.azure.functions.worker.description.FunctionMethodDescriptor;
1722
import com.microsoft.azure.functions.worker.reflect.ClassLoaderProvider;
18-
1923
import org.apache.commons.lang3.exception.ExceptionUtils;
2024
import org.apache.commons.lang3.tuple.ImmutablePair;
2125

@@ -24,6 +28,13 @@
2428
* reflection, and invoke them at runtime. Thread-Safety: Multiple thread.
2529
*/
2630
public class JavaFunctionBroker {
31+
32+
//TODO: build dedicate ImmutablePair class with meaningful fields.
33+
private final Map<String, ImmutablePair<String, FunctionDefinition>> methods;
34+
private final ClassLoaderProvider classLoaderProvider;
35+
private String workerDirectory;
36+
private final AtomicBoolean invocationChainFactoryInitialized = new AtomicBoolean(false);
37+
private volatile InvocationChainFactory invocationChainFactory;
2738
public JavaFunctionBroker(ClassLoaderProvider classLoaderProvider) {
2839
this.methods = new ConcurrentHashMap<>();
2940
this.classLoaderProvider = classLoaderProvider;
@@ -33,15 +44,40 @@ public void loadMethod(FunctionMethodDescriptor descriptor, Map<String, BindingI
3344
throws ClassNotFoundException, NoSuchMethodException, IOException {
3445
descriptor.validate();
3546
addSearchPathsToClassLoader(descriptor);
47+
initializeInvocationChainFactory();
3648
FunctionDefinition functionDefinition = new FunctionDefinition(descriptor, bindings, classLoaderProvider);
3749
this.methods.put(descriptor.getId(), new ImmutablePair<>(descriptor.getName(), functionDefinition));
3850
}
3951

52+
private void initializeInvocationChainFactory() {
53+
if (!invocationChainFactoryInitialized.getAndSet(true)) {
54+
ArrayList<Middleware> middlewares = new ArrayList<>();
55+
try {
56+
//ServiceLoader will use thread context classloader to verify loaded class
57+
Thread.currentThread().setContextClassLoader(classLoaderProvider.createClassLoader());
58+
for (Middleware middleware : ServiceLoader.load(Middleware.class)) {
59+
middlewares.add(middleware);
60+
WorkerLogManager.getSystemLogger().info("Load middleware " + middleware.getClass().getSimpleName());
61+
}
62+
} finally {
63+
Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
64+
}
65+
middlewares.add(getFunctionExecutionMiddleWare());
66+
this.invocationChainFactory = new InvocationChainFactory(middlewares);
67+
}
68+
}
69+
70+
private FunctionExecutionMiddleware getFunctionExecutionMiddleWare() {
71+
FunctionExecutionMiddleware functionExecutionMiddleware = new FunctionExecutionMiddleware(
72+
JavaMethodExecutors.createJavaMethodExecutor(this.classLoaderProvider.createClassLoader()));
73+
WorkerLogManager.getSystemLogger().info("Load last middleware: FunctionExecutionMiddleware");
74+
return functionExecutionMiddleware;
75+
}
76+
4077
public Optional<TypedData> invokeMethod(String id, InvocationRequest request, List<ParameterBinding> outputs)
4178
throws Exception {
4279
ExecutionContextDataSource executionContextDataSource = buildExecutionContext(id, request);
43-
JavaMethodExecutor executor = JavaMethodExecutors.createJavaMethodExecutor(this.classLoaderProvider.createClassLoader());
44-
executor.execute(executionContextDataSource);
80+
this.invocationChainFactory.create().doNext(executionContextDataSource);
4581
outputs.addAll(executionContextDataSource.getDataStore().getOutputParameterBindings(true));
4682
return executionContextDataSource.getDataStore().getDataTargetTypedValue(BindingDataStore.RETURN_NAME);
4783
}
@@ -63,7 +99,7 @@ private ExecutionContextDataSource buildExecutionContext(String id, InvocationR
6399
request.getRetryContext().getMaxRetryCount(), request.getRetryContext().getException());
64100
ExecutionContextDataSource executionContextDataSource = new ExecutionContextDataSource(request.getInvocationId(),
65101
traceContext, retryContext, methodEntry.left, dataStore, functionDefinition.getCandidate(),
66-
functionDefinition.getContainingClass());
102+
functionDefinition.getContainingClass(), request.getInputDataList());
67103
dataStore.addExecutionContextSource(executionContextDataSource);
68104
return executionContextDataSource;
69105
}
@@ -156,9 +192,4 @@ private boolean isTesting(){
156192
public void setWorkerDirectory(String workerDirectory) {
157193
this.workerDirectory = workerDirectory;
158194
}
159-
160-
//TODO: build dedicate ImmutablePair class with meaningful fields.
161-
private final Map<String, ImmutablePair<String, FunctionDefinition>> methods;
162-
private final ClassLoaderProvider classLoaderProvider;
163-
private String workerDirectory;
164195
}

src/main/java/com/microsoft/azure/functions/worker/broker/JavaMethodExecutorImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@ public void execute(ExecutionContextDataSource executionContextDataSource) throw
2020
Object retValue = ParameterResolver.resolveArguments(executionContextDataSource)
2121
.orElseThrow(() -> new NoSuchMethodException("Cannot locate the method signature with the given input"))
2222
.invoke(() -> executionContextDataSource.getContainingClass().newInstance());
23-
executionContextDataSource.getDataStore().setDataTargetValue(BindingDataStore.RETURN_NAME, retValue);
23+
executionContextDataSource.updateReturnValue(retValue);
2424
}
2525
}

src/main/java/com/microsoft/azure/functions/worker/broker/JavaMethodExecutors.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,8 @@
33
import com.microsoft.azure.functions.worker.WorkerLogManager;
44
import org.apache.commons.lang3.SystemUtils;
55

6-
import java.net.MalformedURLException;
7-
86
public class JavaMethodExecutors {
9-
public static JavaMethodExecutor createJavaMethodExecutor(ClassLoader classLoader)
10-
throws MalformedURLException, ClassNotFoundException, NoSuchMethodException {
7+
public static JavaMethodExecutor createJavaMethodExecutor(ClassLoader classLoader) {
118
if(SystemUtils.IS_JAVA_1_8) {
129
WorkerLogManager.getSystemLogger().info("Loading JavaMethodExecutorImpl");
1310
return JavaMethodExecutorImpl.getInstance();

src/main/java/com/microsoft/azure/functions/worker/broker/ParamBindInfo.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ public final class ParamBindInfo {
99
private final Type type;
1010
private final String bindingNameAnnotation;
1111
private final boolean isImplicitOutput;
12+
private final Parameter parameter;
1213
ParamBindInfo(Parameter param) {
1314
this.name = CoreTypeResolver.getAnnotationName(param);
1415
this.type = param.getParameterizedType();
1516
this.bindingNameAnnotation = CoreTypeResolver.getBindingNameAnnotation(param);
1617
this.isImplicitOutput = CoreTypeResolver.checkImplicitOutput(param);
18+
this.parameter = param;
1719
}
1820

1921
public boolean isImplicitOutput() {
@@ -31,4 +33,8 @@ public Type getType() {
3133
public String getBindingNameAnnotation() {
3234
return bindingNameAnnotation;
3335
}
36+
37+
public Parameter getParameter() {
38+
return parameter;
39+
}
3440
}

src/main/java/com/microsoft/azure/functions/worker/broker/ParameterResolver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ private static InvokeInfoBuilder resolve(ExecutionContextDataSource executionCon
4141
argument = dataStore.getOrAddDataTarget(invokeInfo.getOutputsId(), paramName, paramType, false);
4242
}
4343
else if (paramName != null && !paramName.isEmpty()) {
44-
argument = dataStore.getDataByName(paramName, paramType);
44+
argument = executionContextDataSource.getBindingData(paramName, paramType);
4545
}
4646
else if (paramName == null && !paramBindingNameAnnotation.isEmpty()) {
4747
argument = dataStore.getTriggerMetatDataByName(paramBindingNameAnnotation, paramType);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.microsoft.azure.functions.worker.chain;
2+
3+
import com.microsoft.azure.functions.internal.spi.middleware.Middleware;
4+
import com.microsoft.azure.functions.internal.spi.middleware.MiddlewareChain;
5+
import com.microsoft.azure.functions.internal.spi.middleware.MiddlewareContext;
6+
import com.microsoft.azure.functions.worker.binding.ExecutionContextDataSource;
7+
import com.microsoft.azure.functions.worker.broker.JavaMethodExecutor;
8+
9+
public class FunctionExecutionMiddleware implements Middleware {
10+
11+
private final JavaMethodExecutor functionMethodExecutor;
12+
13+
public FunctionExecutionMiddleware(JavaMethodExecutor functionExecutionMiddleware) {
14+
this.functionMethodExecutor = functionExecutionMiddleware;
15+
}
16+
17+
@Override
18+
public void invoke(MiddlewareContext context, MiddlewareChain chain) throws Exception{
19+
this.functionMethodExecutor.execute((ExecutionContextDataSource) context);
20+
}
21+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.microsoft.azure.functions.worker.chain;
2+
3+
4+
import com.microsoft.azure.functions.internal.spi.middleware.Middleware;
5+
import com.microsoft.azure.functions.internal.spi.middleware.MiddlewareChain;
6+
import com.microsoft.azure.functions.internal.spi.middleware.MiddlewareContext;
7+
8+
import java.util.Iterator;
9+
import java.util.List;
10+
11+
public class InvocationChain implements MiddlewareChain {
12+
private final Iterator<Middleware> middlewareIterator;
13+
14+
public InvocationChain(List<Middleware> middlewares){
15+
this.middlewareIterator = middlewares.iterator();
16+
}
17+
18+
@Override
19+
public void doNext(MiddlewareContext context) throws Exception {
20+
if (middlewareIterator.hasNext()) {
21+
middlewareIterator.next().invoke(context, this);
22+
}
23+
}
24+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.microsoft.azure.functions.worker.chain;
2+
3+
4+
import com.microsoft.azure.functions.internal.spi.middleware.Middleware;
5+
import com.microsoft.azure.functions.internal.spi.middleware.MiddlewareChain;
6+
7+
import java.util.List;
8+
9+
public class InvocationChainFactory {
10+
11+
private final List<Middleware> middlewares;
12+
13+
public InvocationChainFactory(List<Middleware> middlewares) {
14+
this.middlewares = middlewares;
15+
}
16+
17+
public MiddlewareChain create(){
18+
return new InvocationChain(middlewares);
19+
}
20+
}

0 commit comments

Comments
 (0)