Skip to content

Commit 098eace

Browse files
authored
Merge pull request #458 from fjtirado/impl_phase2
Adding JQ expression support for http call
2 parents 0863b31 + 7122178 commit 098eace

13 files changed

+525
-36
lines changed

Diff for: impl/pom.xml

+6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<artifactId>serverlessworkflow-impl</artifactId>
99
<properties>
1010
<version.org.glassfish.jersey>3.1.9</version.org.glassfish.jersey>
11+
<version.net.thisptr>1.0.1</version.net.thisptr>
1112
</properties>
1213
<dependencies>
1314
<dependency>
@@ -25,6 +26,11 @@
2526
<artifactId>jersey-media-json-jackson</artifactId>
2627
<version>${version.org.glassfish.jersey}</version>
2728
</dependency>
29+
<dependency>
30+
<groupId>net.thisptr</groupId>
31+
<artifactId>jackson-jq</artifactId>
32+
<version>${version.net.thisptr}</version>
33+
</dependency>
2834
<dependency>
2935
<groupId>org.junit.jupiter</groupId>
3036
<artifactId>junit-jupiter-api</artifactId>

Diff for: impl/src/main/java/io/serverlessworkflow/impl/AbstractTaskExecutor.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@
2121
public abstract class AbstractTaskExecutor<T extends TaskBase> implements TaskExecutor<T> {
2222

2323
protected final T task;
24+
protected final ExpressionFactory exprFactory;
2425

25-
protected AbstractTaskExecutor(T task) {
26+
protected AbstractTaskExecutor(T task, ExpressionFactory exprFactory) {
2627
this.task = task;
28+
this.exprFactory = exprFactory;
2729
}
2830

2931
@Override

Diff for: impl/src/main/java/io/serverlessworkflow/impl/DefaultTaskExecutorFactory.java

+13-4
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,32 @@
1818
import io.serverlessworkflow.api.types.CallTask;
1919
import io.serverlessworkflow.api.types.Task;
2020
import io.serverlessworkflow.api.types.TaskBase;
21+
import io.serverlessworkflow.impl.jq.JQExpressionFactory;
2122

2223
public class DefaultTaskExecutorFactory implements TaskExecutorFactory {
2324

24-
protected DefaultTaskExecutorFactory() {}
25+
private final ExpressionFactory exprFactory;
2526

26-
private static TaskExecutorFactory instance = new DefaultTaskExecutorFactory();
27+
private static TaskExecutorFactory instance =
28+
new DefaultTaskExecutorFactory(JQExpressionFactory.get());
2729

2830
public static TaskExecutorFactory get() {
2931
return instance;
3032
}
3133

32-
public TaskExecutor<? extends TaskBase> getTaskExecutor(Task task) {
34+
public static TaskExecutorFactory get(ExpressionFactory factory) {
35+
return new DefaultTaskExecutorFactory(factory);
36+
}
3337

38+
protected DefaultTaskExecutorFactory(ExpressionFactory exprFactory) {
39+
this.exprFactory = exprFactory;
40+
}
41+
42+
public TaskExecutor<? extends TaskBase> getTaskExecutor(Task task) {
3443
if (task.getCallTask() != null) {
3544
CallTask callTask = task.getCallTask();
3645
if (callTask.getCallHTTP() != null) {
37-
return new HttpExecutor(callTask.getCallHTTP());
46+
return new HttpExecutor(callTask.getCallHTTP(), exprFactory);
3847
}
3948
}
4049
throw new UnsupportedOperationException(task.get().getClass().getName() + " not supported yet");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright 2020-Present The Serverless Workflow Specification Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.serverlessworkflow.impl;
17+
18+
import com.fasterxml.jackson.databind.JsonNode;
19+
20+
public interface Expression {
21+
JsonNode eval(JsonNode input);
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright 2020-Present The Serverless Workflow Specification Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.serverlessworkflow.impl;
17+
18+
public interface ExpressionFactory {
19+
20+
Expression getExpression(String expression);
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2020-Present The Serverless Workflow Specification Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.serverlessworkflow.impl;
17+
18+
public class ExpressionUtils {
19+
20+
private static final String EXPR_PREFIX = "${";
21+
private static final String EXPR_SUFFIX = "}";
22+
23+
private ExpressionUtils() {}
24+
25+
public static String trimExpr(String expr) {
26+
expr = expr.trim();
27+
if (expr.startsWith(EXPR_PREFIX)) {
28+
expr = trimExpr(expr, EXPR_PREFIX, EXPR_SUFFIX);
29+
}
30+
return expr.trim();
31+
}
32+
33+
private static String trimExpr(String expr, String prefix, String suffix) {
34+
expr = expr.substring(prefix.length());
35+
if (expr.endsWith(suffix)) {
36+
expr = expr.substring(0, expr.length() - suffix.length());
37+
}
38+
return expr;
39+
}
40+
}

Diff for: impl/src/main/java/io/serverlessworkflow/impl/HttpExecutor.java

+79-16
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
import com.fasterxml.jackson.core.type.TypeReference;
1919
import com.fasterxml.jackson.databind.JsonNode;
2020
import io.serverlessworkflow.api.types.CallHTTP;
21+
import io.serverlessworkflow.api.types.Endpoint;
22+
import io.serverlessworkflow.api.types.EndpointUri;
2123
import io.serverlessworkflow.api.types.HTTPArguments;
24+
import io.serverlessworkflow.api.types.UriTemplate;
2225
import io.serverlessworkflow.api.types.WithHTTPHeaders;
2326
import io.serverlessworkflow.api.types.WithHTTPQuery;
2427
import jakarta.ws.rs.HttpMethod;
@@ -27,40 +30,33 @@
2730
import jakarta.ws.rs.client.Entity;
2831
import jakarta.ws.rs.client.Invocation.Builder;
2932
import jakarta.ws.rs.client.WebTarget;
33+
import java.net.URI;
3034
import java.util.Map;
3135
import java.util.Map.Entry;
36+
import java.util.function.Function;
3237

3338
public class HttpExecutor extends AbstractTaskExecutor<CallHTTP> {
3439

3540
private static final Client client = ClientBuilder.newClient();
3641

37-
public HttpExecutor(CallHTTP task) {
38-
super(task);
42+
private final Function<JsonNode, WebTarget> targetSupplier;
43+
44+
public HttpExecutor(CallHTTP task, ExpressionFactory factory) {
45+
super(task, factory);
46+
this.targetSupplier = getTargetSupplier(task.getWith().getEndpoint());
3947
}
4048

4149
@Override
4250
protected JsonNode internalExecute(JsonNode node) {
4351
HTTPArguments httpArgs = task.getWith();
44-
// missing checks
45-
String uri =
46-
httpArgs
47-
.getEndpoint()
48-
.getEndpointConfiguration()
49-
.getUri()
50-
.getLiteralEndpointURI()
51-
.getLiteralUriTemplate();
52-
WebTarget target = client.target(uri);
5352
WithHTTPQuery query = httpArgs.getQuery();
53+
WebTarget target = targetSupplier.apply(node);
5454
if (query != null) {
5555
for (Entry<String, Object> entry : query.getAdditionalProperties().entrySet()) {
5656
target = target.queryParam(entry.getKey(), entry.getValue());
5757
}
5858
}
59-
Builder request =
60-
target
61-
.resolveTemplates(
62-
JsonUtils.mapper().convertValue(node, new TypeReference<Map<String, Object>>() {}))
63-
.request();
59+
Builder request = target.request();
6460
WithHTTPHeaders headers = httpArgs.getHeaders();
6561
if (headers != null) {
6662
headers.getAdditionalProperties().forEach(request::header);
@@ -73,4 +69,71 @@ protected JsonNode internalExecute(JsonNode node) {
7369
return request.post(Entity.json(httpArgs.getBody()), JsonNode.class);
7470
}
7571
}
72+
73+
private Function<JsonNode, WebTarget> getTargetSupplier(Endpoint endpoint) {
74+
if (endpoint.getEndpointConfiguration() != null) {
75+
EndpointUri uri = endpoint.getEndpointConfiguration().getUri();
76+
if (uri.getLiteralEndpointURI() != null) {
77+
return getURISupplier(uri.getLiteralEndpointURI());
78+
} else if (uri.getExpressionEndpointURI() != null) {
79+
return new ExpressionURISupplier(uri.getExpressionEndpointURI());
80+
}
81+
} else if (endpoint.getRuntimeExpression() != null) {
82+
return new ExpressionURISupplier(endpoint.getRuntimeExpression());
83+
} else if (endpoint.getUriTemplate() != null) {
84+
return getURISupplier(endpoint.getUriTemplate());
85+
}
86+
throw new IllegalArgumentException("Invalid endpoint definition " + endpoint);
87+
}
88+
89+
private Function<JsonNode, WebTarget> getURISupplier(UriTemplate template) {
90+
if (template.getLiteralUri() != null) {
91+
return new URISupplier(template.getLiteralUri());
92+
} else if (template.getLiteralUriTemplate() != null) {
93+
return new URITemplateSupplier(template.getLiteralUriTemplate());
94+
}
95+
throw new IllegalArgumentException("Invalid uritemplate definition " + template);
96+
}
97+
98+
private class URISupplier implements Function<JsonNode, WebTarget> {
99+
private final URI uri;
100+
101+
public URISupplier(URI uri) {
102+
this.uri = uri;
103+
}
104+
105+
@Override
106+
public WebTarget apply(JsonNode input) {
107+
return client.target(uri);
108+
}
109+
}
110+
111+
private class URITemplateSupplier implements Function<JsonNode, WebTarget> {
112+
private final String uri;
113+
114+
public URITemplateSupplier(String uri) {
115+
this.uri = uri;
116+
}
117+
118+
@Override
119+
public WebTarget apply(JsonNode input) {
120+
return client
121+
.target(uri)
122+
.resolveTemplates(
123+
JsonUtils.mapper().convertValue(input, new TypeReference<Map<String, Object>>() {}));
124+
}
125+
}
126+
127+
private class ExpressionURISupplier implements Function<JsonNode, WebTarget> {
128+
private Expression expr;
129+
130+
public ExpressionURISupplier(String expr) {
131+
this.expr = exprFactory.getExpression(expr);
132+
}
133+
134+
@Override
135+
public WebTarget apply(JsonNode input) {
136+
return client.target(expr.eval(input).asText());
137+
}
138+
}
76139
}

Diff for: impl/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java

+8-10
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,22 @@ public class WorkflowDefinition {
3333

3434
private WorkflowDefinition(
3535
Workflow workflow,
36-
TaskExecutorFactory factory,
36+
TaskExecutorFactory taskFactory,
3737
Collection<WorkflowExecutionListener> listeners) {
3838
this.workflow = workflow;
39-
this.factory = factory;
39+
this.taskFactory = taskFactory;
4040
this.listeners = listeners;
4141
}
4242

4343
private final Workflow workflow;
4444
private final Collection<WorkflowExecutionListener> listeners;
45-
private final TaskExecutorFactory factory;
45+
private final TaskExecutorFactory taskFactory;
4646
private final Map<JsonPointer, TaskExecutor<? extends TaskBase>> taskExecutors =
4747
new ConcurrentHashMap<>();
4848

4949
public static class Builder {
5050
private final Workflow workflow;
51-
private TaskExecutorFactory factory = DefaultTaskExecutorFactory.get();
51+
private TaskExecutorFactory taskFactory = DefaultTaskExecutorFactory.get();
5252
private Collection<WorkflowExecutionListener> listeners;
5353

5454
private Builder(Workflow workflow) {
@@ -64,14 +64,14 @@ public Builder withListener(WorkflowExecutionListener listener) {
6464
}
6565

6666
public Builder withTaskExecutorFactory(TaskExecutorFactory factory) {
67-
this.factory = factory;
67+
this.taskFactory = factory;
6868
return this;
6969
}
7070

7171
public WorkflowDefinition build() {
7272
return new WorkflowDefinition(
7373
workflow,
74-
factory,
74+
taskFactory,
7575
listeners == null
7676
? Collections.emptySet()
7777
: Collections.unmodifiableCollection(listeners));
@@ -83,7 +83,7 @@ public static Builder builder(Workflow workflow) {
8383
}
8484

8585
public WorkflowInstance execute(Object input) {
86-
return new WorkflowInstance(factory, JsonUtils.fromValue(input));
86+
return new WorkflowInstance(taskFactory, JsonUtils.fromValue(input));
8787
}
8888

8989
enum State {
@@ -97,7 +97,6 @@ public class WorkflowInstance {
9797
private final JsonNode input;
9898
private JsonNode output;
9999
private State state;
100-
private TaskExecutorFactory factory;
101100

102101
private JsonPointer currentPos;
103102

@@ -106,7 +105,6 @@ private WorkflowInstance(TaskExecutorFactory factory, JsonNode input) {
106105
this.output = object();
107106
this.state = State.STARTED;
108107
this.currentPos = JsonPointer.compile("/");
109-
this.factory = factory;
110108
processDo(workflow.getDo());
111109
}
112110

@@ -119,7 +117,7 @@ private void processDo(List<TaskItem> tasks) {
119117
this.output =
120118
MergeUtils.merge(
121119
taskExecutors
122-
.computeIfAbsent(currentPos, k -> factory.getTaskExecutor(task.getTask()))
120+
.computeIfAbsent(currentPos, k -> taskFactory.getTaskExecutor(task.getTask()))
123121
.apply(input),
124122
output);
125123
listeners.forEach(l -> l.onTaskEnded(currentPos, task.getTask()));

0 commit comments

Comments
 (0)