Skip to content

Commit c4cf709

Browse files
authored
[Fix #1014] Supporting PUT, PATCH, OPTIONS, HEAD, DELETE methods (#1015)
Signed-off-by: fjtirado <[email protected]>
1 parent f5a623d commit c4cf709

File tree

13 files changed

+211
-37
lines changed

13 files changed

+211
-37
lines changed

impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowError.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ public static Builder communication(int status, TaskContext context, String titl
4242
.title(title);
4343
}
4444

45+
public static Builder communication(int status, TaskContext context) {
46+
return new Builder(Errors.COMMUNICATION.toString(), status)
47+
.instance(context.position().jsonPointer());
48+
}
49+
4550
public static Builder communication(TaskContext context, String title) {
4651
return communication(Errors.COMMUNICATION.status(), context, title);
4752
}

impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/AbstractHttpExecutorBuilder.java

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@
1616
package io.serverlessworkflow.impl.executors.http;
1717

1818
import io.serverlessworkflow.impl.WorkflowApplication;
19-
import io.serverlessworkflow.impl.WorkflowFilter;
20-
import io.serverlessworkflow.impl.WorkflowUtils;
2119
import io.serverlessworkflow.impl.WorkflowValueResolver;
2220
import jakarta.ws.rs.HttpMethod;
21+
import jakarta.ws.rs.client.Invocation;
2322
import jakarta.ws.rs.client.WebTarget;
2423
import java.net.URI;
2524
import java.util.Map;
@@ -32,30 +31,27 @@ abstract class AbstractHttpExecutorBuilder {
3231
protected WorkflowValueResolver<Map<String, Object>> queryMap;
3332
protected Optional<AuthProvider> authProvider = Optional.empty();
3433
protected RequestSupplier requestFunction;
35-
protected boolean redirect;
3634

3735
protected static RequestSupplier buildRequestSupplier(
38-
String method, Object body, WorkflowApplication application) {
36+
String method, Object body, boolean redirect, WorkflowApplication application) {
3937

4038
switch (method.toUpperCase()) {
4139
case HttpMethod.POST:
42-
WorkflowFilter bodyFilter = WorkflowUtils.buildWorkflowFilter(application, body);
43-
return (request, w, t, node) -> {
44-
HttpModelConverter converter = HttpConverterResolver.converter(w, t);
45-
return w.definition()
46-
.application()
47-
.modelFactory()
48-
.fromAny(
49-
request.post(
50-
converter.toEntity(bodyFilter.apply(w, t, node)), converter.responseType()));
51-
};
40+
return new WithBodyRequestSupplier(Invocation.Builder::post, application, body, redirect);
41+
case HttpMethod.PUT:
42+
return new WithBodyRequestSupplier(Invocation.Builder::put, application, body, redirect);
43+
case HttpMethod.DELETE:
44+
return new WithoutBodyRequestSupplier(Invocation.Builder::delete, application, redirect);
45+
case HttpMethod.HEAD:
46+
return new WithoutBodyRequestSupplier(Invocation.Builder::head, application, redirect);
47+
case HttpMethod.OPTIONS:
48+
return new WithoutBodyRequestSupplier(Invocation.Builder::options, application, redirect);
49+
case HttpMethod.PATCH:
50+
return new WithBodyRequestSupplier(
51+
(request, entity) -> request.method("patch", entity), application, body, redirect);
5252
case HttpMethod.GET:
5353
default:
54-
return (request, w, t, n) ->
55-
w.definition()
56-
.application()
57-
.modelFactory()
58-
.fromAny(request.get(HttpConverterResolver.converter(w, t).responseType()));
54+
return new WithoutBodyRequestSupplier(Invocation.Builder::get, application, redirect);
5955
}
6056
}
6157

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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.executors.http;
17+
18+
import io.serverlessworkflow.impl.TaskContext;
19+
import io.serverlessworkflow.impl.WorkflowContext;
20+
import io.serverlessworkflow.impl.WorkflowError;
21+
import io.serverlessworkflow.impl.WorkflowException;
22+
import io.serverlessworkflow.impl.WorkflowModel;
23+
import jakarta.ws.rs.client.Invocation.Builder;
24+
import jakarta.ws.rs.core.Response;
25+
import jakarta.ws.rs.core.Response.Status.Family;
26+
27+
abstract class AbstractRequestSupplier implements RequestSupplier {
28+
29+
private final boolean redirect;
30+
31+
public AbstractRequestSupplier(boolean redirect) {
32+
this.redirect = redirect;
33+
}
34+
35+
@Override
36+
public WorkflowModel apply(
37+
Builder request, WorkflowContext workflow, TaskContext task, WorkflowModel model) {
38+
HttpModelConverter converter = HttpConverterResolver.converter(workflow, task);
39+
Response response = invokeRequest(request, converter, workflow, task, model);
40+
validateStatus(task, response, converter);
41+
return workflow
42+
.definition()
43+
.application()
44+
.modelFactory()
45+
.fromAny(response.readEntity(converter.responseType()));
46+
}
47+
48+
private void validateStatus(TaskContext task, Response response, HttpModelConverter converter) {
49+
if (response.getStatusInfo().getFamily() != Family.SUCCESSFUL) {
50+
throw new WorkflowException(
51+
converter
52+
.errorFromResponse(WorkflowError.communication(response.getStatus(), task), response)
53+
.build());
54+
}
55+
}
56+
57+
protected abstract Response invokeRequest(
58+
Builder request,
59+
HttpModelConverter converter,
60+
WorkflowContext workflow,
61+
TaskContext task,
62+
WorkflowModel model);
63+
}

impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/CallableTaskHttpExecutorBuilder.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,10 @@ public void init(CallHTTP task, WorkflowDefinition definition, WorkflowMutablePo
6363
}
6464
this.requestFunction =
6565
buildRequestSupplier(
66-
httpArgs.getMethod().toUpperCase(), httpArgs.getBody(), definition.application());
66+
httpArgs.getMethod().toUpperCase(),
67+
httpArgs.getBody(),
68+
httpArgs.isRedirect(),
69+
definition.application());
6770
}
6871

6972
@Override

impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,9 @@
1717

1818
import io.serverlessworkflow.impl.TaskContext;
1919
import io.serverlessworkflow.impl.WorkflowContext;
20-
import io.serverlessworkflow.impl.WorkflowError;
21-
import io.serverlessworkflow.impl.WorkflowException;
2220
import io.serverlessworkflow.impl.WorkflowModel;
2321
import io.serverlessworkflow.impl.WorkflowValueResolver;
2422
import io.serverlessworkflow.impl.executors.CallableTask;
25-
import jakarta.ws.rs.WebApplicationException;
2623
import jakarta.ws.rs.client.Invocation.Builder;
2724
import jakarta.ws.rs.client.WebTarget;
2825
import java.util.Map;
@@ -79,15 +76,8 @@ public CompletableFuture<WorkflowModel> apply(
7976
h -> h.apply(workflow, taskContext, input).forEach((k, v) -> request.header(k, v)));
8077
return CompletableFuture.supplyAsync(
8178
() -> {
82-
try {
83-
authProvider.ifPresent(auth -> auth.build(request, workflow, taskContext, input));
84-
return requestFunction.apply(request, workflow, taskContext, input);
85-
} catch (WebApplicationException exception) {
86-
throw new WorkflowException(
87-
WorkflowError.communication(
88-
exception.getResponse().getStatus(), taskContext, exception)
89-
.build());
90-
}
79+
authProvider.ifPresent(auth -> auth.build(request, workflow, taskContext, input));
80+
return requestFunction.apply(request, workflow, taskContext, input);
9181
},
9282
workflow.definition().application().executorService());
9383
}

impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutorBuilder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public class HttpExecutorBuilder extends AbstractHttpExecutorBuilder {
3030
private WorkflowValueResolver<URI> pathSupplier;
3131
private Object body;
3232
private String method = HttpMethod.GET;
33+
private boolean redirect;
3334

3435
private HttpExecutorBuilder(WorkflowDefinition definition) {
3536
this.definition = definition;
@@ -83,7 +84,7 @@ public HttpExecutor build(String uri) {
8384
}
8485

8586
public HttpExecutor build(WorkflowValueResolver<URI> uriSupplier) {
86-
this.requestFunction = buildRequestSupplier(method, body, definition.application());
87+
this.requestFunction = buildRequestSupplier(method, body, redirect, definition.application());
8788
this.targetSupplier =
8889
pathSupplier == null
8990
? getTargetSupplier(uriSupplier)

impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpModelConverter.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515
*/
1616
package io.serverlessworkflow.impl.executors.http;
1717

18+
import io.serverlessworkflow.impl.WorkflowError;
1819
import io.serverlessworkflow.impl.WorkflowModel;
1920
import jakarta.ws.rs.client.Entity;
21+
import jakarta.ws.rs.core.Response;
22+
import org.slf4j.LoggerFactory;
2023

2124
public interface HttpModelConverter {
2225

@@ -25,4 +28,19 @@ default Entity<?> toEntity(WorkflowModel model) {
2528
}
2629

2730
Class<?> responseType();
31+
32+
default WorkflowError.Builder errorFromResponse(
33+
WorkflowError.Builder errorBuilder, Response response) {
34+
try {
35+
Object title = response.readEntity(responseType());
36+
if (title != null) {
37+
errorBuilder.title(title.toString());
38+
}
39+
} catch (Exception ex) {
40+
LoggerFactory.getLogger(HttpModelConverter.class)
41+
.warn("Problem extracting error from http response", ex);
42+
}
43+
44+
return errorBuilder;
45+
}
2846
}

impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/RequestSupplier.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@
2323
@FunctionalInterface
2424
interface RequestSupplier {
2525
WorkflowModel apply(
26-
Builder request, WorkflowContext workflow, TaskContext task, WorkflowModel node);
26+
Builder request, WorkflowContext workflow, TaskContext task, WorkflowModel model);
2727
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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.executors.http;
17+
18+
import io.serverlessworkflow.impl.TaskContext;
19+
import io.serverlessworkflow.impl.WorkflowApplication;
20+
import io.serverlessworkflow.impl.WorkflowContext;
21+
import io.serverlessworkflow.impl.WorkflowFilter;
22+
import io.serverlessworkflow.impl.WorkflowModel;
23+
import io.serverlessworkflow.impl.WorkflowUtils;
24+
import jakarta.ws.rs.client.Entity;
25+
import jakarta.ws.rs.client.Invocation.Builder;
26+
import jakarta.ws.rs.core.Response;
27+
import java.util.function.BiFunction;
28+
29+
class WithBodyRequestSupplier extends AbstractRequestSupplier {
30+
private final WorkflowFilter bodyFilter;
31+
private final BiFunction<Builder, Entity<?>, Response> requestFunction;
32+
33+
public WithBodyRequestSupplier(
34+
BiFunction<Builder, Entity<?>, Response> requestFunction,
35+
WorkflowApplication application,
36+
Object body,
37+
boolean redirect) {
38+
super(redirect);
39+
this.requestFunction = requestFunction;
40+
bodyFilter = WorkflowUtils.buildWorkflowFilter(application, body);
41+
}
42+
43+
@Override
44+
protected Response invokeRequest(
45+
Builder request,
46+
HttpModelConverter converter,
47+
WorkflowContext workflow,
48+
TaskContext task,
49+
WorkflowModel model) {
50+
return requestFunction.apply(
51+
request, converter.toEntity(bodyFilter.apply(workflow, task, model)));
52+
}
53+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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.executors.http;
17+
18+
import io.serverlessworkflow.impl.TaskContext;
19+
import io.serverlessworkflow.impl.WorkflowApplication;
20+
import io.serverlessworkflow.impl.WorkflowContext;
21+
import io.serverlessworkflow.impl.WorkflowModel;
22+
import jakarta.ws.rs.client.Invocation.Builder;
23+
import jakarta.ws.rs.core.Response;
24+
import java.util.function.Function;
25+
26+
class WithoutBodyRequestSupplier extends AbstractRequestSupplier {
27+
private final Function<Builder, Response> requestFunction;
28+
29+
public WithoutBodyRequestSupplier(
30+
Function<Builder, Response> requestFunction,
31+
WorkflowApplication application,
32+
boolean redirect) {
33+
super(redirect);
34+
this.requestFunction = requestFunction;
35+
}
36+
37+
@Override
38+
protected Response invokeRequest(
39+
Builder request,
40+
HttpModelConverter converter,
41+
WorkflowContext workflow,
42+
TaskContext task,
43+
WorkflowModel model) {
44+
return requestFunction.apply(request);
45+
}
46+
}

0 commit comments

Comments
 (0)