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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy