Skip to content

Commit 786ad48

Browse files
committed
[Fix serverlessworkflow#467] Adding schema validation
Signed-off-by: Francisco Javier Tirado Sarti <[email protected]>
1 parent 4e4f3a0 commit 786ad48

23 files changed

+723
-135
lines changed

impl/pom.xml

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +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>
11+
<version.net.thisptr>1.1.0</version.net.thisptr>
1212
</properties>
1313
<dependencies>
1414
<dependency>
@@ -26,6 +26,10 @@
2626
<artifactId>jersey-media-json-jackson</artifactId>
2727
<version>${version.org.glassfish.jersey}</version>
2828
</dependency>
29+
<dependency>
30+
<groupId>com.networknt</groupId>
31+
<artifactId>json-schema-validator</artifactId>
32+
</dependency>
2933
<dependency>
3034
<groupId>net.thisptr</groupId>
3135
<artifactId>jackson-jq</artifactId>

impl/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java

+62-10
Original file line numberDiff line numberDiff line change
@@ -15,44 +15,77 @@
1515
*/
1616
package io.serverlessworkflow.impl;
1717

18+
import static io.serverlessworkflow.impl.WorkflowUtils.*;
1819
import static io.serverlessworkflow.impl.json.JsonUtils.*;
1920

2021
import com.fasterxml.jackson.databind.JsonNode;
22+
import io.serverlessworkflow.api.types.Input;
23+
import io.serverlessworkflow.api.types.Output;
2124
import io.serverlessworkflow.api.types.TaskBase;
2225
import io.serverlessworkflow.api.types.TaskItem;
2326
import io.serverlessworkflow.api.types.Workflow;
2427
import io.serverlessworkflow.impl.executors.DefaultTaskExecutorFactory;
2528
import io.serverlessworkflow.impl.executors.TaskExecutor;
2629
import io.serverlessworkflow.impl.executors.TaskExecutorFactory;
30+
import io.serverlessworkflow.impl.expressions.ExpressionFactory;
31+
import io.serverlessworkflow.impl.expressions.JQExpressionFactory;
2732
import io.serverlessworkflow.impl.json.JsonUtils;
33+
import io.serverlessworkflow.impl.jsonschema.DefaultSchemaValidatorFactory;
34+
import io.serverlessworkflow.impl.jsonschema.SchemaValidator;
35+
import io.serverlessworkflow.impl.jsonschema.SchemaValidatorFactory;
36+
import io.serverlessworkflow.resources.DefaultResourceLoader;
37+
import io.serverlessworkflow.resources.ResourceLoader;
2838
import java.util.Collection;
2939
import java.util.Collections;
3040
import java.util.HashSet;
3141
import java.util.List;
3242
import java.util.Map;
43+
import java.util.Optional;
3344
import java.util.concurrent.ConcurrentHashMap;
3445

3546
public class WorkflowDefinition {
3647

3748
private WorkflowDefinition(
3849
Workflow workflow,
39-
TaskExecutorFactory taskFactory,
40-
Collection<WorkflowExecutionListener> listeners) {
50+
Collection<WorkflowExecutionListener> listeners,
51+
WorkflowFactories factories) {
4152
this.workflow = workflow;
42-
this.taskFactory = taskFactory;
4353
this.listeners = listeners;
54+
this.factories = factories;
55+
if (workflow.getInput() != null) {
56+
Input input = workflow.getInput();
57+
this.inputSchemaValidator =
58+
getSchemaValidator(
59+
factories.getValidatorFactory(), schemaToNode(factories, input.getSchema()));
60+
this.inputFilter = buildWorkflowFilter(factories.getExpressionFactory(), input.getFrom());
61+
}
62+
if (workflow.getOutput() != null) {
63+
Output output = workflow.getOutput();
64+
this.outputSchemaValidator =
65+
getSchemaValidator(
66+
factories.getValidatorFactory(), schemaToNode(factories, output.getSchema()));
67+
this.outputFilter = buildWorkflowFilter(factories.getExpressionFactory(), output.getAs());
68+
}
4469
}
4570

4671
private final Workflow workflow;
4772
private final Collection<WorkflowExecutionListener> listeners;
48-
private final TaskExecutorFactory taskFactory;
73+
private final WorkflowFactories factories;
74+
private Optional<SchemaValidator> inputSchemaValidator = Optional.empty();
75+
private Optional<SchemaValidator> outputSchemaValidator = Optional.empty();
76+
private Optional<WorkflowFilter> inputFilter = Optional.empty();
77+
private Optional<WorkflowFilter> outputFilter = Optional.empty();
78+
4979
private final Map<String, TaskExecutor<? extends TaskBase>> taskExecutors =
5080
new ConcurrentHashMap<>();
5181

5282
public static class Builder {
5383
private final Workflow workflow;
5484
private TaskExecutorFactory taskFactory = DefaultTaskExecutorFactory.get();
85+
private ExpressionFactory exprFactory = JQExpressionFactory.get();
5586
private Collection<WorkflowExecutionListener> listeners;
87+
private ResourceLoader resourceLoader = DefaultResourceLoader.get();
88+
private SchemaValidatorFactory schemaValidatorFactory = DefaultSchemaValidatorFactory.get();
5689

5790
private Builder(Workflow workflow) {
5891
this.workflow = workflow;
@@ -71,13 +104,28 @@ public Builder withTaskExecutorFactory(TaskExecutorFactory factory) {
71104
return this;
72105
}
73106

107+
public Builder withExpressionFactory(ExpressionFactory factory) {
108+
this.exprFactory = factory;
109+
return this;
110+
}
111+
112+
public Builder withResourceLoader(ResourceLoader resourceLoader) {
113+
this.resourceLoader = resourceLoader;
114+
return this;
115+
}
116+
117+
public Builder withSchemaValidatorFactory(SchemaValidatorFactory factory) {
118+
this.schemaValidatorFactory = factory;
119+
return this;
120+
}
121+
74122
public WorkflowDefinition build() {
75123
return new WorkflowDefinition(
76124
workflow,
77-
taskFactory,
78125
listeners == null
79126
? Collections.emptySet()
80-
: Collections.unmodifiableCollection(listeners));
127+
: Collections.unmodifiableCollection(listeners),
128+
new WorkflowFactories(taskFactory, resourceLoader, exprFactory, schemaValidatorFactory));
81129
}
82130
}
83131

@@ -86,7 +134,7 @@ public static Builder builder(Workflow workflow) {
86134
}
87135

88136
public WorkflowInstance execute(Object input) {
89-
return new WorkflowInstance(taskFactory, JsonUtils.fromValue(input));
137+
return new WorkflowInstance(JsonUtils.fromValue(input));
90138
}
91139

92140
enum State {
@@ -101,11 +149,15 @@ public class WorkflowInstance {
101149
private State state;
102150
private WorkflowContext context;
103151

104-
private WorkflowInstance(TaskExecutorFactory factory, JsonNode input) {
152+
private WorkflowInstance(JsonNode input) {
105153
this.output = input;
106-
this.state = State.STARTED;
154+
inputSchemaValidator.ifPresent(v -> v.validate(input));
107155
this.context = WorkflowContext.builder(input).build();
156+
inputFilter.ifPresent(f -> output = f.apply(context, Optional.empty(), output));
157+
this.state = State.STARTED;
108158
processDo(workflow.getDo());
159+
outputFilter.ifPresent(f -> output = f.apply(context, Optional.empty(), output));
160+
outputSchemaValidator.ifPresent(v -> v.validate(output));
109161
}
110162

111163
private void processDo(List<TaskItem> tasks) {
@@ -118,7 +170,7 @@ private void processDo(List<TaskItem> tasks) {
118170
taskExecutors
119171
.computeIfAbsent(
120172
context.position().jsonPointer(),
121-
k -> taskFactory.getTaskExecutor(task.getTask()))
173+
k -> factories.getTaskFactory().getTaskExecutor(task.getTask(), factories))
122174
.apply(context, output);
123175
listeners.forEach(l -> l.onTaskEnded(context.position(), task.getTask()));
124176
context.position().back().back();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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 io.serverlessworkflow.impl.executors.TaskExecutorFactory;
19+
import io.serverlessworkflow.impl.expressions.ExpressionFactory;
20+
import io.serverlessworkflow.impl.jsonschema.SchemaValidatorFactory;
21+
import io.serverlessworkflow.resources.ResourceLoader;
22+
23+
public class WorkflowFactories {
24+
25+
private final TaskExecutorFactory taskFactory;
26+
private final ResourceLoader resourceLoader;
27+
private final ExpressionFactory expressionFactory;
28+
private final SchemaValidatorFactory validatorFactory;
29+
30+
public WorkflowFactories(
31+
TaskExecutorFactory taskFactory,
32+
ResourceLoader loaderFactory,
33+
ExpressionFactory expressionFactory,
34+
SchemaValidatorFactory validatorFactory) {
35+
this.taskFactory = taskFactory;
36+
this.resourceLoader = loaderFactory;
37+
this.expressionFactory = expressionFactory;
38+
this.validatorFactory = validatorFactory;
39+
}
40+
41+
public TaskExecutorFactory getTaskFactory() {
42+
return taskFactory;
43+
}
44+
45+
public ResourceLoader getResourceLoader() {
46+
return resourceLoader;
47+
}
48+
49+
public ExpressionFactory getExpressionFactory() {
50+
return expressionFactory;
51+
}
52+
53+
public SchemaValidatorFactory getValidatorFactory() {
54+
return validatorFactory;
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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+
import java.util.Optional;
20+
21+
@FunctionalInterface
22+
public interface WorkflowFilter {
23+
JsonNode apply(WorkflowContext workflow, Optional<TaskContext<?>> task, JsonNode node);
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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+
import io.serverlessworkflow.api.types.ExportAs;
20+
import io.serverlessworkflow.api.types.InputFrom;
21+
import io.serverlessworkflow.api.types.OutputAs;
22+
import io.serverlessworkflow.api.types.SchemaExternal;
23+
import io.serverlessworkflow.api.types.SchemaInline;
24+
import io.serverlessworkflow.api.types.SchemaUnion;
25+
import io.serverlessworkflow.impl.expressions.Expression;
26+
import io.serverlessworkflow.impl.expressions.ExpressionFactory;
27+
import io.serverlessworkflow.impl.expressions.ExpressionUtils;
28+
import io.serverlessworkflow.impl.json.JsonUtils;
29+
import io.serverlessworkflow.impl.jsonschema.SchemaValidator;
30+
import io.serverlessworkflow.impl.jsonschema.SchemaValidatorFactory;
31+
import java.io.IOException;
32+
import java.io.InputStream;
33+
import java.io.UncheckedIOException;
34+
import java.util.Map;
35+
import java.util.Optional;
36+
37+
public class WorkflowUtils {
38+
39+
private WorkflowUtils() {}
40+
41+
public static Optional<SchemaValidator> getSchemaValidator(
42+
SchemaValidatorFactory validatorFactory, Optional<JsonNode> node) {
43+
return node.map(n -> validatorFactory.getValidator(n));
44+
}
45+
46+
public static Optional<JsonNode> schemaToNode(WorkflowFactories factories, SchemaUnion schema) {
47+
if (schema != null) {
48+
if (schema.getSchemaInline() != null) {
49+
SchemaInline inline = schema.getSchemaInline();
50+
return Optional.of(JsonUtils.mapper().convertValue(inline.getDocument(), JsonNode.class));
51+
} else if (schema.getSchemaExternal() != null) {
52+
SchemaExternal external = schema.getSchemaExternal();
53+
try (InputStream in =
54+
factories.getResourceLoader().loadStatic(external.getResource()).open()) {
55+
return Optional.of(JsonUtils.mapper().readTree(in));
56+
} catch (IOException io) {
57+
throw new UncheckedIOException(io);
58+
}
59+
}
60+
}
61+
return Optional.empty();
62+
}
63+
64+
public static Optional<WorkflowFilter> buildWorkflowFilter(
65+
ExpressionFactory exprFactory, InputFrom from) {
66+
return from != null
67+
? Optional.of(buildWorkflowFilter(exprFactory, from.getString(), from.getObject()))
68+
: Optional.empty();
69+
}
70+
71+
public static Optional<WorkflowFilter> buildWorkflowFilter(
72+
ExpressionFactory exprFactory, OutputAs as) {
73+
return as != null
74+
? Optional.of(buildWorkflowFilter(exprFactory, as.getString(), as.getObject()))
75+
: Optional.empty();
76+
}
77+
78+
public static Optional<WorkflowFilter> buildWorkflowFilter(
79+
ExpressionFactory exprFactory, ExportAs as) {
80+
return as != null
81+
? Optional.of(buildWorkflowFilter(exprFactory, as.getString(), as.getObject()))
82+
: Optional.empty();
83+
}
84+
85+
private static WorkflowFilter buildWorkflowFilter(
86+
ExpressionFactory exprFactory, String str, Object object) {
87+
if (str != null) {
88+
Expression expression = exprFactory.getExpression(str);
89+
return expression::eval;
90+
} else {
91+
Object exprObj = ExpressionUtils.buildExpressionObject(object, exprFactory);
92+
return exprObj instanceof Map
93+
? (w, t, n) ->
94+
JsonUtils.fromValue(
95+
ExpressionUtils.evaluateExpressionMap((Map<String, Object>) exprObj, w, t, n))
96+
: (w, t, n) -> JsonUtils.fromValue(object);
97+
}
98+
}
99+
}

0 commit comments

Comments
 (0)