Skip to content

Commit 3c09007

Browse files
committed
[Fix #467] Adding schema validation
Signed-off-by: Francisco Javier Tirado Sarti <ftirados@redhat.com>
1 parent 4e4f3a0 commit 3c09007

29 files changed

+886
-139
lines changed

impl/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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/WorkflowContext.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import io.serverlessworkflow.impl.json.JsonUtils;
2020

2121
public class WorkflowContext {
22-
2322
private final WorkflowPosition position;
2423
private JsonNode context;
2524
private final JsonNode input;

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

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,44 +15,79 @@
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.DefaultResourceLoaderFactory;
37+
import io.serverlessworkflow.resources.ResourceLoaderFactory;
38+
import java.nio.file.Path;
2839
import java.util.Collection;
2940
import java.util.Collections;
3041
import java.util.HashSet;
3142
import java.util.List;
3243
import java.util.Map;
44+
import java.util.Optional;
3345
import java.util.concurrent.ConcurrentHashMap;
3446

3547
public class WorkflowDefinition {
3648

3749
private WorkflowDefinition(
3850
Workflow workflow,
39-
TaskExecutorFactory taskFactory,
40-
Collection<WorkflowExecutionListener> listeners) {
51+
Collection<WorkflowExecutionListener> listeners,
52+
WorkflowFactories factories) {
4153
this.workflow = workflow;
42-
this.taskFactory = taskFactory;
4354
this.listeners = listeners;
55+
this.factories = factories;
56+
if (workflow.getInput() != null) {
57+
Input input = workflow.getInput();
58+
this.inputSchemaValidator =
59+
getSchemaValidator(
60+
factories.getValidatorFactory(), schemaToNode(factories, input.getSchema()));
61+
this.inputFilter = buildWorkflowFilter(factories.getExpressionFactory(), input.getFrom());
62+
}
63+
if (workflow.getOutput() != null) {
64+
Output output = workflow.getOutput();
65+
this.outputSchemaValidator =
66+
getSchemaValidator(
67+
factories.getValidatorFactory(), schemaToNode(factories, output.getSchema()));
68+
this.outputFilter = buildWorkflowFilter(factories.getExpressionFactory(), output.getAs());
69+
}
4470
}
4571

4672
private final Workflow workflow;
4773
private final Collection<WorkflowExecutionListener> listeners;
48-
private final TaskExecutorFactory taskFactory;
74+
private final WorkflowFactories factories;
75+
private Optional<SchemaValidator> inputSchemaValidator = Optional.empty();
76+
private Optional<SchemaValidator> outputSchemaValidator = Optional.empty();
77+
private Optional<WorkflowFilter> inputFilter = Optional.empty();
78+
private Optional<WorkflowFilter> outputFilter = Optional.empty();
79+
4980
private final Map<String, TaskExecutor<? extends TaskBase>> taskExecutors =
5081
new ConcurrentHashMap<>();
5182

5283
public static class Builder {
5384
private final Workflow workflow;
5485
private TaskExecutorFactory taskFactory = DefaultTaskExecutorFactory.get();
86+
private ExpressionFactory exprFactory = JQExpressionFactory.get();
5587
private Collection<WorkflowExecutionListener> listeners;
88+
private ResourceLoaderFactory resourceLoaderFactory = DefaultResourceLoaderFactory.get();
89+
private SchemaValidatorFactory schemaValidatorFactory = DefaultSchemaValidatorFactory.get();
90+
private Path path;
5691

5792
private Builder(Workflow workflow) {
5893
this.workflow = workflow;
@@ -71,13 +106,39 @@ public Builder withTaskExecutorFactory(TaskExecutorFactory factory) {
71106
return this;
72107
}
73108

109+
public Builder withExpressionFactory(ExpressionFactory factory) {
110+
this.exprFactory = factory;
111+
return this;
112+
}
113+
114+
public Builder withPath(Path path) {
115+
this.path = path;
116+
return this;
117+
}
118+
119+
public Builder withResourceLoaderFactory(ResourceLoaderFactory resourceLoader) {
120+
this.resourceLoaderFactory = resourceLoader;
121+
return this;
122+
}
123+
124+
public Builder withSchemaValidatorFactory(SchemaValidatorFactory factory) {
125+
this.schemaValidatorFactory = factory;
126+
return this;
127+
}
128+
74129
public WorkflowDefinition build() {
75-
return new WorkflowDefinition(
76-
workflow,
77-
taskFactory,
78-
listeners == null
79-
? Collections.emptySet()
80-
: Collections.unmodifiableCollection(listeners));
130+
WorkflowDefinition def =
131+
new WorkflowDefinition(
132+
workflow,
133+
listeners == null
134+
? Collections.emptySet()
135+
: Collections.unmodifiableCollection(listeners),
136+
new WorkflowFactories(
137+
taskFactory,
138+
resourceLoaderFactory.getResourceLoader(path),
139+
exprFactory,
140+
schemaValidatorFactory));
141+
return def;
81142
}
82143
}
83144

@@ -86,7 +147,7 @@ public static Builder builder(Workflow workflow) {
86147
}
87148

88149
public WorkflowInstance execute(Object input) {
89-
return new WorkflowInstance(taskFactory, JsonUtils.fromValue(input));
150+
return new WorkflowInstance(JsonUtils.fromValue(input));
90151
}
91152

92153
enum State {
@@ -101,11 +162,15 @@ public class WorkflowInstance {
101162
private State state;
102163
private WorkflowContext context;
103164

104-
private WorkflowInstance(TaskExecutorFactory factory, JsonNode input) {
165+
private WorkflowInstance(JsonNode input) {
105166
this.output = input;
106-
this.state = State.STARTED;
167+
inputSchemaValidator.ifPresent(v -> v.validate(input));
107168
this.context = WorkflowContext.builder(input).build();
169+
inputFilter.ifPresent(f -> output = f.apply(context, Optional.empty(), output));
170+
this.state = State.STARTED;
108171
processDo(workflow.getDo());
172+
outputFilter.ifPresent(f -> output = f.apply(context, Optional.empty(), output));
173+
outputSchemaValidator.ifPresent(v -> v.validate(output));
109174
}
110175

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

0 commit comments

Comments
 (0)