Skip to content

Commit c1895b5

Browse files
authored
refactor java worker - add middleware support (#652)
* decouple function definition and function executor * add function exceptions * remove unnecessary synchronized block * review updates * fix invocatioid error * rename factory get method * 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 * update core lib version to 1.1.0
1 parent d9bf967 commit c1895b5

19 files changed

+506
-297
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
2121
<maven.compiler.source>1.8</maven.compiler.source>
2222
<maven.compiler.target>1.8</maven.compiler.target>
23-
<azure.functions.java.core.library.version>1.0.0</azure.functions.java.core.library.version>
23+
<azure.functions.java.core.library.version>1.1.0</azure.functions.java.core.library.version>
2424
<azure.functions.java.library.version>2.0.1</azure.functions.java.library.version>
2525
</properties>
2626
<licenses>

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

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,23 +47,24 @@ public void addTriggerMetadataSource(Map<String, TypedData> metadata) {
4747
}
4848
}
4949

50-
public void addExecutionContextSource(String invocationId, String funcname, ExecutionTraceContext traceContext, ExecutionRetryContext retryContext) {
51-
otherSources.put(ExecutionContext.class,
52-
new ExecutionContextDataSource(
53-
invocationId,
54-
funcname,
55-
traceContext,
56-
retryContext
57-
)
58-
);
50+
public void addExecutionContextSource(ExecutionContextDataSource executionContextDataSource) {
51+
otherSources.put(ExecutionContext.class,executionContextDataSource);
5952
}
6053

6154
public Optional<BindingData> getDataByName(String name, Type target) {
62-
return this.inputSources.get(name).computeByName(name, target);
55+
DataSource<?> parameterDataSource = this.inputSources.get(name);
56+
if (parameterDataSource == null) {
57+
throw new RuntimeException("Cannot find matched parameter name of customer function, please check if customer function is defined correctly");
58+
}
59+
return parameterDataSource.computeByName(name, target);
6360
}
6461

6562
public Optional<BindingData> getTriggerMetatDataByName(String name, Type target) {
66-
return this.metadataSources.get(name).computeByName(name, target);
63+
DataSource<?> metadataDataSource = this.metadataSources.get(name);
64+
if (metadataDataSource == null) {
65+
throw new RuntimeException("Cannot find matched @BindingName of customer function, please check if customer function is defined correctly");
66+
}
67+
return metadataDataSource.computeByName(name, target);
6768
}
6869

6970
public Optional<BindingData> getDataByType(Type target) {
Lines changed: 145 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,74 @@
11
package com.microsoft.azure.functions.worker.binding;
22

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;
7+
import com.microsoft.azure.functions.worker.WorkerLogManager;
8+
import com.microsoft.azure.functions.worker.broker.MethodBindInfo;
9+
import com.microsoft.azure.functions.worker.broker.ParamBindInfo;
10+
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;
318
import java.util.logging.Logger;
419

5-
import com.microsoft.azure.functions.ExecutionContext;
6-
import com.microsoft.azure.functions.worker.WorkerLogManager;
7-
import com.microsoft.azure.functions.TraceContext;
8-
import com.microsoft.azure.functions.RetryContext;
20+
public final class ExecutionContextDataSource extends DataSource<ExecutionContext> implements MiddlewareContext {
21+
private final String invocationId;
22+
private final TraceContext traceContext;
23+
private final RetryContext retryContext;
24+
private final Logger logger;
25+
private final String funcname;
26+
private final BindingDataStore dataStore;
27+
private final MethodBindInfo methodBindInfo;
28+
private final Class<?> containingClass;
29+
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
53+
private static final DataOperations<ExecutionContext, Object> EXECONTEXT_DATA_OPERATIONS = new DataOperations<>();
54+
static {
55+
EXECONTEXT_DATA_OPERATIONS.addGenericOperation(ExecutionContext.class, DataOperations::generalAssignment);
56+
}
957

10-
final class ExecutionContextDataSource extends DataSource<ExecutionContext> implements ExecutionContext {
11-
ExecutionContextDataSource(String invocationId, String funcname, TraceContext traceContext, RetryContext retryContext) {
58+
public ExecutionContextDataSource(String invocationId, TraceContext traceContext, RetryContext retryContext,
59+
String funcname, BindingDataStore dataStore, MethodBindInfo methodBindInfo,
60+
Class<?> containingClass, List<ParameterBinding> parameterBindings){
1261
super(null, null, EXECONTEXT_DATA_OPERATIONS);
1362
this.invocationId = invocationId;
1463
this.traceContext = traceContext;
1564
this.retryContext = retryContext;
1665
this.logger = WorkerLogManager.getInvocationLogger(invocationId);
1766
this.funcname = funcname;
67+
this.dataStore = dataStore;
68+
this.methodBindInfo = methodBindInfo;
69+
this.containingClass = containingClass;
70+
this.parameterDefinitions = getParameterDefinitions(methodBindInfo);
71+
this.parameterValues = resolveParameterValuesForMiddleware(parameterBindings);
1872
this.setValue(this);
1973
}
2074

@@ -32,15 +86,90 @@ final class ExecutionContextDataSource extends DataSource<ExecutionContext> impl
3286

3387
@Override
3488
public String getFunctionName() { return this.funcname; }
35-
36-
private final String invocationId;
37-
private final TraceContext traceContext;
38-
private final RetryContext retryContext;
39-
private final Logger logger;
40-
private final String funcname;
4189

42-
private static final DataOperations<ExecutionContext, Object> EXECONTEXT_DATA_OPERATIONS = new DataOperations<>();
43-
static {
44-
EXECONTEXT_DATA_OPERATIONS.addGenericOperation(ExecutionContext.class, DataOperations::generalAssignment);
45-
}
90+
public BindingDataStore getDataStore() {
91+
return dataStore;
92+
}
93+
94+
public MethodBindInfo getMethodBindInfo() {
95+
return methodBindInfo;
96+
}
97+
98+
public Class<?> getContainingClass() {
99+
return containingClass;
100+
}
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+
}
46175
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ static String getBindingNameAnnotation(Parameter param) {
121121
return new String("");
122122
}
123123

124-
static boolean checkHasImplicitOutput(Parameter parameter) {
124+
static boolean checkImplicitOutput(Parameter parameter) {
125125
Annotation[] annotations = parameter.getAnnotations();
126126
for (Annotation annotation : annotations) {
127127
for (Annotation item : annotation.annotationType().getAnnotations()) {
Lines changed: 7 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,28 @@
11
package com.microsoft.azure.functions.worker.broker;
22

3-
import java.lang.reflect.*;
4-
import java.util.*;
5-
63
import com.microsoft.azure.functions.worker.binding.*;
7-
import com.microsoft.azure.functions.worker.description.*;
8-
import com.microsoft.azure.functions.worker.reflect.*;
9-
import com.microsoft.azure.functions.rpc.messages.*;
104

115
/**
126
* Used to executor of arbitrary Java method in any JAR using reflection.
137
* Thread-Safety: Multiple thread.
148
*/
159
public class EnhancedJavaMethodExecutorImpl implements JavaMethodExecutor {
16-
public EnhancedJavaMethodExecutorImpl(FunctionMethodDescriptor descriptor, Map<String, BindingInfo> bindingInfos, ClassLoaderProvider classLoaderProvider)
17-
throws ClassNotFoundException, NoSuchMethodException
18-
{
19-
descriptor.validateMethodInfo();
20-
21-
this.classLoader = classLoaderProvider.createClassLoader();
22-
this.containingClass = getContainingClass(descriptor.getFullClassName());
23-
this.overloadResolver = new ParameterResolver();
24-
25-
for (Method method : this.containingClass.getMethods()) {
26-
if (method.getDeclaringClass().getName().equals(descriptor.getFullClassName()) && method.getName().equals(descriptor.getMethodName())) {
27-
this.overloadResolver.addCandidate(method);
28-
}
29-
}
30-
31-
if (!this.overloadResolver.hasCandidates()) {
32-
throw new NoSuchMethodException("There are no methods named \"" + descriptor.getName() + "\" in class \"" + descriptor.getFullClassName() + "\"");
33-
}
34-
35-
if (this.overloadResolver.hasMultipleCandidates()) {
36-
throw new UnsupportedOperationException("Found more than one function with method name \"" + descriptor.getName() + "\" in class \"" + descriptor.getFullClassName() + "\"");
37-
}
3810

39-
this.bindingDefinitions = new HashMap<>();
11+
private final ClassLoader classLoader;
4012

41-
for (Map.Entry<String, BindingInfo> entry : bindingInfos.entrySet()) {
42-
this.bindingDefinitions.put(entry.getKey(), new BindingDefinition(entry.getKey(), entry.getValue()));
43-
}
13+
public EnhancedJavaMethodExecutorImpl(ClassLoader classLoader) {
14+
this.classLoader = classLoader;
4415
}
4516

46-
public Map<String, BindingDefinition> getBindingDefinitions() { return this.bindingDefinitions; }
47-
48-
public ParameterResolver getOverloadResolver() { return this.overloadResolver; }
49-
50-
public void execute(BindingDataStore dataStore) throws Exception {
17+
public void execute(ExecutionContextDataSource executionContextDataSource) throws Exception {
5118
try {
5219
Thread.currentThread().setContextClassLoader(this.classLoader);
53-
Object retValue = this.overloadResolver.resolve(dataStore)
20+
Object retValue = ParameterResolver.resolveArguments(executionContextDataSource)
5421
.orElseThrow(() -> new NoSuchMethodException("Cannot locate the method signature with the given input"))
55-
.invoke(() -> this.containingClass.newInstance());
56-
dataStore.setDataTargetValue(BindingDataStore.RETURN_NAME, retValue);
22+
.invoke(() -> executionContextDataSource.getContainingClass().newInstance());
23+
executionContextDataSource.updateReturnValue(retValue);
5724
} finally {
5825
Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
5926
}
6027
}
61-
62-
private Class<?> getContainingClass(String className) throws ClassNotFoundException {
63-
return Class.forName(className, false, this.classLoader);
64-
}
65-
66-
private final Class<?> containingClass;
67-
private final ClassLoader classLoader;
68-
private final ParameterResolver overloadResolver;
69-
private final Map<String, BindingDefinition> bindingDefinitions;
7028
}

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

Lines changed: 0 additions & 23 deletions
This file was deleted.

0 commit comments

Comments
 (0)