diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..06541a8b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "maven" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + assignees: + - ricardozanini + - tsurdilo diff --git a/.github/project.yml b/.github/project.yml new file mode 100644 index 00000000..a69b4050 --- /dev/null +++ b/.github/project.yml @@ -0,0 +1,3 @@ +release: + current-version: 4.0.5 + next-version: 5.0.0-SNAPSHOT \ No newline at end of file diff --git a/.github/workflows/maven-deploy.yml b/.github/workflows/maven-deploy.yml deleted file mode 100644 index 67812473..00000000 --- a/.github/workflows/maven-deploy.yml +++ /dev/null @@ -1,26 +0,0 @@ -# This workflow will build a Java project with Maven -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven - -name: Deploy JAVA SDK - -on: - push: - branches: - - main -jobs: - publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Maven Central Repository - uses: actions/setup-java@v1 - with: - java-version: 1.8 - server-id: ossrh - server-username: MAVEN_USERNAME - server-password: MAVEN_PASSWORD - - name: Publish package - run: mvn -B -f pom.xml deploy - env: - MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} diff --git a/.github/workflows/maven-verify.yml b/.github/workflows/maven-verify.yml index 5da52578..44aba632 100644 --- a/.github/workflows/maven-verify.yml +++ b/.github/workflows/maven-verify.yml @@ -1,30 +1,28 @@ # This workflow will build a Java project with Maven # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven -name: Verify JAVA SDK +name: sdk-java Verify on: push: branches: - - main + - 4.0.* pull_request: branches: - - main + - 4.0.* jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 - with: - java-version: 1.8 - - name: Cache Maven packages - uses: actions/cache@v2 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('pom.xml') }} - restore-keys: ${{ runner.os }}-m2 - - name: Verify with Maven - run: | - mvn -B -f pom.xml clean install verify + - uses: actions/checkout@v2 + + - name: Set up JDK 1.8 + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 8 + cache: 'maven' + + - name: Verify with Maven + run: | + mvn -B -f pom.xml clean install verify diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml new file mode 100644 index 00000000..ce904c75 --- /dev/null +++ b/.github/workflows/pre-release.yml @@ -0,0 +1,25 @@ +name: sdk-java Pre Release + +on: + pull_request: + paths: + - '.github/project.yml' + +jobs: + release: + runs-on: ubuntu-latest + name: pre release + + steps: + - uses: radcortez/project-metadata-action@master + name: retrieve project metadata + id: metadata + with: + github-token: ${{secrets.GITHUB_TOKEN}} + metadata-file-path: '.github/project.yml' + + - name: Validate version + if: contains(steps.metadata.outputs.current-version, 'SNAPSHOT') + run: | + echo '::error::Cannot release a SNAPSHOT version.' + exit 1 \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..f2341c8a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,58 @@ +name: sdk-java Release + +on: + pull_request: + types: [closed] + paths: + - '.github/project.yml' + +jobs: + release: + runs-on: ubuntu-latest + name: release + if: ${{github.event.pull_request.merged == true}} + + steps: + - uses: radcortez/project-metadata-action@main + name: Retrieve project metadata + id: metadata + with: + github-token: ${{secrets.GITHUB_TOKEN}} + metadata-file-path: '.github/project.yml' + + - uses: actions/checkout@v3 + + - name: Import GPG key + id: import_gpg + uses: crazy-max/ghaction-import-gpg@v5 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.GPG_PASSPHRASE }} + + - name: Set up JDK 1.8 + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 8 + cache: 'maven' + server-id: ossrh + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + + - name: Configure Git author + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + + - name: Maven release ${{steps.metadata.outputs.current-version}} + run: | + mvn -B release:prepare -Prelease -DreleaseVersion=${{steps.metadata.outputs.current-version}} -DdevelopmentVersion=${{steps.metadata.outputs.next-version}} + mvn -B release:perform -Darguments=-DperformRelease -DperformRelease -Prelease + env: + MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + + - name: Push changes to ${{github.base_ref}} branch + run: | + git push + git push origin ${{steps.metadata.outputs.current-version}} diff --git a/api/pom.xml b/api/pom.xml index 8b27e853..63ba1ef1 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -26,31 +26,19 @@ com.fasterxml.jackson.core jackson-core - [2.13.0,) com.fasterxml.jackson.core jackson-databind - [2.13.0,) com.fasterxml.jackson.dataformat jackson-dataformat-yaml - [2.13.0,) javax.validation validation-api - - org.json - json - - - com.github.erosb - everit-json-schema - - org.junit.jupiter diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/AuthDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/AuthDeserializer.java index abdb0583..aa078cb4 100644 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/AuthDeserializer.java +++ b/api/src/main/java/io/serverlessworkflow/api/deserializers/AuthDeserializer.java @@ -20,7 +20,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import io.serverlessworkflow.api.auth.AuthDefinition; import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; import io.serverlessworkflow.api.utils.Utils; @@ -28,7 +27,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,21 +67,8 @@ public Auth deserialize(JsonParser jp, DeserializationContext ctxt) throws IOExc } else { String authFileDef = node.asText(); String authFileSrc = Utils.getResourceFileAsString(authFileDef); - JsonNode authRefNode; - ObjectMapper jsonWriter = new ObjectMapper(); if (authFileSrc != null && authFileSrc.trim().length() > 0) { - // if its a yaml def convert to json first - if (!authFileSrc.trim().startsWith("{")) { - // convert yaml to json to validate - ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory()); - Object obj = yamlReader.readValue(authFileSrc, Object.class); - - authRefNode = - jsonWriter.readTree(new JSONObject(jsonWriter.writeValueAsString(obj)).toString()); - } else { - authRefNode = jsonWriter.readTree(new JSONObject(authFileSrc).toString()); - } - + JsonNode authRefNode = Utils.getNode(authFileSrc); JsonNode refAuth = authRefNode.get("auth"); if (refAuth != null) { for (final JsonNode nodeEle : refAuth) { diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/ConstantsDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/ConstantsDeserializer.java index c3789b52..3859273c 100644 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/ConstantsDeserializer.java +++ b/api/src/main/java/io/serverlessworkflow/api/deserializers/ConstantsDeserializer.java @@ -18,14 +18,11 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; import io.serverlessworkflow.api.utils.Utils; import io.serverlessworkflow.api.workflow.Constants; import java.io.IOException; -import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -63,21 +60,8 @@ public Constants deserialize(JsonParser jp, DeserializationContext ctxt) throws } else { String constantsFileDef = node.asText(); String constantsFileSrc = Utils.getResourceFileAsString(constantsFileDef); - JsonNode constantsRefNode; - ObjectMapper jsonWriter = new ObjectMapper(); if (constantsFileSrc != null && constantsFileSrc.trim().length() > 0) { - // if its a yaml def convert to json first - if (!constantsFileSrc.trim().startsWith("{")) { - // convert yaml to json to validate - ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory()); - Object obj = yamlReader.readValue(constantsFileSrc, Object.class); - - constantsRefNode = - jsonWriter.readTree(new JSONObject(jsonWriter.writeValueAsString(obj)).toString()); - } else { - constantsRefNode = jsonWriter.readTree(new JSONObject(constantsFileSrc).toString()); - } - + JsonNode constantsRefNode = Utils.getNode(constantsFileSrc); JsonNode refConstants = constantsRefNode.get("constants"); if (refConstants != null) { constantsDefinition = refConstants; diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/ErrorsDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/ErrorsDeserializer.java index beedc7dd..6fe366ea 100644 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/ErrorsDeserializer.java +++ b/api/src/main/java/io/serverlessworkflow/api/deserializers/ErrorsDeserializer.java @@ -20,7 +20,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import io.serverlessworkflow.api.error.ErrorDefinition; import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; import io.serverlessworkflow.api.utils.Utils; @@ -28,7 +27,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,21 +67,8 @@ public Errors deserialize(JsonParser jp, DeserializationContext ctxt) throws IOE } else { String errorsFileDef = node.asText(); String errorsFileSrc = Utils.getResourceFileAsString(errorsFileDef); - JsonNode errorsRefNode; - ObjectMapper jsonWriter = new ObjectMapper(); if (errorsFileSrc != null && errorsFileSrc.trim().length() > 0) { - // if its a yaml def convert to json first - if (!errorsFileSrc.trim().startsWith("{")) { - // convert yaml to json to validate - ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory()); - Object obj = yamlReader.readValue(errorsFileSrc, Object.class); - - errorsRefNode = - jsonWriter.readTree(new JSONObject(jsonWriter.writeValueAsString(obj)).toString()); - } else { - errorsRefNode = jsonWriter.readTree(new JSONObject(errorsFileSrc).toString()); - } - + JsonNode errorsRefNode = Utils.getNode(errorsFileSrc); JsonNode refErrors = errorsRefNode.get("errors"); if (refErrors != null) { for (final JsonNode nodeEle : refErrors) { diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/EventsDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/EventsDeserializer.java index cfa207df..a02fdf4b 100644 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/EventsDeserializer.java +++ b/api/src/main/java/io/serverlessworkflow/api/deserializers/EventsDeserializer.java @@ -20,7 +20,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import io.serverlessworkflow.api.events.EventDefinition; import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; import io.serverlessworkflow.api.utils.Utils; @@ -28,7 +27,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,21 +67,9 @@ public Events deserialize(JsonParser jp, DeserializationContext ctxt) throws IOE } else { String eventsFileDef = node.asText(); String eventsFileSrc = Utils.getResourceFileAsString(eventsFileDef); - JsonNode eventsRefNode; - ObjectMapper jsonWriter = new ObjectMapper(); if (eventsFileSrc != null && eventsFileSrc.trim().length() > 0) { // if its a yaml def convert to json first - if (!eventsFileSrc.trim().startsWith("{")) { - // convert yaml to json to validate - ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory()); - Object obj = yamlReader.readValue(eventsFileSrc, Object.class); - - eventsRefNode = - jsonWriter.readTree(new JSONObject(jsonWriter.writeValueAsString(obj)).toString()); - } else { - eventsRefNode = jsonWriter.readTree(new JSONObject(eventsFileSrc).toString()); - } - + JsonNode eventsRefNode = Utils.getNode(eventsFileSrc); JsonNode refEvents = eventsRefNode.get("events"); if (refEvents != null) { for (final JsonNode nodeEle : refEvents) { diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/FunctionsDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/FunctionsDeserializer.java index c27e2c48..b706b2d3 100644 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/FunctionsDeserializer.java +++ b/api/src/main/java/io/serverlessworkflow/api/deserializers/FunctionsDeserializer.java @@ -20,7 +20,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import io.serverlessworkflow.api.functions.FunctionDefinition; import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; import io.serverlessworkflow.api.utils.Utils; @@ -28,7 +27,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,20 +67,9 @@ public Functions deserialize(JsonParser jp, DeserializationContext ctxt) throws String functionsFileDef = node.asText(); String functionsFileSrc = Utils.getResourceFileAsString(functionsFileDef); JsonNode functionsRefNode; - ObjectMapper jsonWriter = new ObjectMapper(); if (functionsFileSrc != null && functionsFileSrc.trim().length() > 0) { // if its a yaml def convert to json first - if (!functionsFileSrc.trim().startsWith("{")) { - // convert yaml to json to validate - ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory()); - Object obj = yamlReader.readValue(functionsFileSrc, Object.class); - - functionsRefNode = - jsonWriter.readTree(new JSONObject(jsonWriter.writeValueAsString(obj)).toString()); - } else { - functionsRefNode = jsonWriter.readTree(new JSONObject(functionsFileSrc).toString()); - } - + functionsRefNode = Utils.getNode(functionsFileSrc); JsonNode refFunctions = functionsRefNode.get("functions"); if (refFunctions != null) { for (final JsonNode nodeEle : refFunctions) { diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/RetriesDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/RetriesDeserializer.java index ff2fe44d..9eb47b5f 100644 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/RetriesDeserializer.java +++ b/api/src/main/java/io/serverlessworkflow/api/deserializers/RetriesDeserializer.java @@ -20,7 +20,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; import io.serverlessworkflow.api.retry.RetryDefinition; import io.serverlessworkflow.api.utils.Utils; @@ -28,7 +27,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,21 +67,8 @@ public Retries deserialize(JsonParser jp, DeserializationContext ctxt) throws IO } else { String retriesFileDef = node.asText(); String retriesFileSrc = Utils.getResourceFileAsString(retriesFileDef); - JsonNode retriesRefNode; - ObjectMapper jsonWriter = new ObjectMapper(); if (retriesFileSrc != null && retriesFileSrc.trim().length() > 0) { - // if its a yaml def convert to json first - if (!retriesFileSrc.trim().startsWith("{")) { - // convert yaml to json to validate - ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory()); - Object obj = yamlReader.readValue(retriesFileSrc, Object.class); - - retriesRefNode = - jsonWriter.readTree(new JSONObject(jsonWriter.writeValueAsString(obj)).toString()); - } else { - retriesRefNode = jsonWriter.readTree(new JSONObject(retriesFileSrc).toString()); - } - + JsonNode retriesRefNode = Utils.getNode(retriesFileSrc); JsonNode refRetries = retriesRefNode.get("retries"); if (refRetries != null) { for (final JsonNode nodeEle : refRetries) { diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/SecretsDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/SecretsDeserializer.java index e9ec05e7..60cc2a82 100644 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/SecretsDeserializer.java +++ b/api/src/main/java/io/serverlessworkflow/api/deserializers/SecretsDeserializer.java @@ -18,16 +18,13 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; import io.serverlessworkflow.api.utils.Utils; import io.serverlessworkflow.api.workflow.Secrets; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,21 +63,9 @@ public Secrets deserialize(JsonParser jp, DeserializationContext ctxt) throws IO } else { String secretsFileDef = node.asText(); String secretsFileSrc = Utils.getResourceFileAsString(secretsFileDef); - JsonNode secretsRefNode; - ObjectMapper jsonWriter = new ObjectMapper(); if (secretsFileSrc != null && secretsFileSrc.trim().length() > 0) { // if its a yaml def convert to json first - if (!secretsFileSrc.trim().startsWith("{")) { - // convert yaml to json to validate - ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory()); - Object obj = yamlReader.readValue(secretsFileSrc, Object.class); - - secretsRefNode = - jsonWriter.readTree(new JSONObject(jsonWriter.writeValueAsString(obj)).toString()); - } else { - secretsRefNode = jsonWriter.readTree(new JSONObject(secretsFileSrc).toString()); - } - + JsonNode secretsRefNode = Utils.getNode(secretsFileSrc); JsonNode refSecrets = secretsRefNode.get("secrets"); if (refSecrets != null) { for (final JsonNode nodeEle : refSecrets) { diff --git a/api/src/main/java/io/serverlessworkflow/api/mapper/JsonObjectMapperFactory.java b/api/src/main/java/io/serverlessworkflow/api/mapper/JsonObjectMapperFactory.java new file mode 100644 index 00000000..eb34b0eb --- /dev/null +++ b/api/src/main/java/io/serverlessworkflow/api/mapper/JsonObjectMapperFactory.java @@ -0,0 +1,27 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.api.mapper; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class JsonObjectMapperFactory { + + private static final ObjectMapper instance = new JsonObjectMapper(); + + public static final ObjectMapper mapper() { + return instance; + } +} diff --git a/api/src/main/java/io/serverlessworkflow/api/mapper/YamlObjectMapperFactory.java b/api/src/main/java/io/serverlessworkflow/api/mapper/YamlObjectMapperFactory.java new file mode 100644 index 00000000..04371db4 --- /dev/null +++ b/api/src/main/java/io/serverlessworkflow/api/mapper/YamlObjectMapperFactory.java @@ -0,0 +1,27 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.api.mapper; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class YamlObjectMapperFactory { + + private static final ObjectMapper instance = new YamlObjectMapper(); + + public static final ObjectMapper mapper() { + return instance; + } +} diff --git a/api/src/main/java/io/serverlessworkflow/api/schemaclient/ResourceSchemaClient.java b/api/src/main/java/io/serverlessworkflow/api/schemaclient/ResourceSchemaClient.java deleted file mode 100644 index a4da2387..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/schemaclient/ResourceSchemaClient.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2020-Present The Serverless Workflow Specification Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.serverlessworkflow.api.schemaclient; - -import java.io.InputStream; -import java.util.Objects; -import org.everit.json.schema.loader.SchemaClient; - -public class ResourceSchemaClient implements SchemaClient { - - @SuppressWarnings("unused") - private final SchemaClient fallbackClient; - - private final String baseResourcePath = "/schema/"; - - public ResourceSchemaClient(SchemaClient fallbackClient) { - this.fallbackClient = Objects.requireNonNull(fallbackClient, "fallbackClient cannot be null"); - } - - @Override - public InputStream get(String path) { - path = path.substring("https://wg-serverless.org/".length()); - return this.getClass().getResourceAsStream(baseResourcePath + path); - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/utils/Utils.java b/api/src/main/java/io/serverlessworkflow/api/utils/Utils.java index 3e4b4274..9bdce416 100644 --- a/api/src/main/java/io/serverlessworkflow/api/utils/Utils.java +++ b/api/src/main/java/io/serverlessworkflow/api/utils/Utils.java @@ -15,6 +15,11 @@ */ package io.serverlessworkflow.api.utils; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.serverlessworkflow.api.mapper.JsonObjectMapperFactory; +import io.serverlessworkflow.api.mapper.YamlObjectMapperFactory; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -34,4 +39,14 @@ public static String getResourceFileAsString(String fileName) throws IOException } } } + + public static ObjectMapper getObjectMapper(String source) { + return !source.trim().startsWith("{") + ? YamlObjectMapperFactory.mapper() + : JsonObjectMapperFactory.mapper(); + } + + public static JsonNode getNode(String source) throws JsonProcessingException { + return getObjectMapper(source).readTree(source); + } } diff --git a/api/src/main/java/io/serverlessworkflow/api/validation/WorkflowSchemaLoader.java b/api/src/main/java/io/serverlessworkflow/api/validation/WorkflowSchemaLoader.java index 830bb50a..847380fb 100644 --- a/api/src/main/java/io/serverlessworkflow/api/validation/WorkflowSchemaLoader.java +++ b/api/src/main/java/io/serverlessworkflow/api/validation/WorkflowSchemaLoader.java @@ -15,26 +15,23 @@ */ package io.serverlessworkflow.api.validation; -import io.serverlessworkflow.api.schemaclient.ResourceSchemaClient; -import org.everit.json.schema.Schema; -import org.everit.json.schema.loader.SchemaLoader; -import org.everit.json.schema.loader.internal.DefaultSchemaClient; -import org.json.JSONObject; -import org.json.JSONTokener; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.io.UncheckedIOException; public class WorkflowSchemaLoader { - private static final JSONObject workflowSchema = - new JSONObject( - new JSONTokener(WorkflowSchemaLoader.class.getResourceAsStream("/schema/workflow.json"))); - public static Schema getWorkflowSchema() { - SchemaLoader schemaLoader = - SchemaLoader.builder() - .schemaClient(new ResourceSchemaClient(new DefaultSchemaClient())) - .schemaJson(workflowSchema) - .resolutionScope("classpath:schema") - .draftV7Support() - .build(); - return schemaLoader.load().build(); + public static JsonNode getWorkflowSchema() { + try { + return ObjectMapperHolder.objectMapper.readTree( + WorkflowSchemaLoader.class.getResourceAsStream("/schema/workflow.json")); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static class ObjectMapperHolder { + public static final ObjectMapper objectMapper = new ObjectMapper(); } } diff --git a/api/src/main/resources/schema/produce/produceevent.json b/api/src/main/resources/schema/produce/produceevent.json index b5bb7d5f..f094824e 100644 --- a/api/src/main/resources/schema/produce/produceevent.json +++ b/api/src/main/resources/schema/produce/produceevent.json @@ -10,6 +10,11 @@ "data": { "type": "string", "description": "Workflow expression which selects parts of the states data output to become the data of the produced event" + }, + "contextAttributes": { + "type": "object", + "description": "Add additional event extension context attributes", + "existingJavaType": "java.util.Map" } }, "required": [ diff --git a/api/src/main/resources/schema/workflow.json b/api/src/main/resources/schema/workflow.json index 3ed9ce58..f8309b6c 100644 --- a/api/src/main/resources/schema/workflow.json +++ b/api/src/main/resources/schema/workflow.json @@ -1,5 +1,5 @@ { - "$id": "https://wg-serverless.org/workflow.schema.json", + "$id": "classpath:schema/workflow.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "description": "Serverless Workflow is a vendor-neutral specification for defining the model of workflows responsible for orchestrating event-driven serverless applications.", "type": "object", @@ -11,7 +11,8 @@ "properties": { "id": { "type": "string", - "description": "Workflow unique identifier" + "description": "Workflow unique identifier", + "minLength": 1 }, "key": { "type": "string", @@ -159,6 +160,7 @@ } }, "required": [ + "id", "name", "version", "states" diff --git a/api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java b/api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java index a1854d43..f5b30d07 100644 --- a/api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java +++ b/api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java @@ -44,6 +44,7 @@ import io.serverlessworkflow.api.workflow.Retries; import io.serverlessworkflow.api.workflow.Secrets; import java.util.List; +import java.util.Map; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -264,6 +265,12 @@ public void testTransitions(String workflowLocation) { assertEquals("RejectApplication", cond2.getTransition().getNextState()); assertNotNull(cond2.getTransition().getProduceEvents()); assertEquals(1, cond2.getTransition().getProduceEvents().size()); + assertNotNull(cond2.getTransition().getProduceEvents().get(0).getContextAttributes()); + Map contextAttributes = + cond2.getTransition().getProduceEvents().get(0).getContextAttributes(); + assertEquals(2, contextAttributes.size()); + assertEquals("IN", contextAttributes.get("order_location")); + assertEquals("online", contextAttributes.get("order_type")); assertFalse(cond2.getTransition().isCompensate()); assertNotNull(switchState.getDefaultCondition()); diff --git a/api/src/test/resources/features/transitions.json b/api/src/test/resources/features/transitions.json index cacc94af..ed7b7626 100644 --- a/api/src/test/resources/features/transitions.json +++ b/api/src/test/resources/features/transitions.json @@ -23,7 +23,11 @@ "produceEvents": [ { "eventRef": "provisioningCompleteEvent", - "data": "${ .provisionedOrders }" + "data": "${ .provisionedOrders }", + "contextAttributes": { + "order_location": "IN", + "order_type": "online" + } } ] } diff --git a/api/src/test/resources/features/transitions.yml b/api/src/test/resources/features/transitions.yml index 1b89a85f..3ec34ae4 100644 --- a/api/src/test/resources/features/transitions.yml +++ b/api/src/test/resources/features/transitions.yml @@ -18,6 +18,9 @@ states: produceEvents: - eventRef: provisioningCompleteEvent data: "${ .provisionedOrders }" + contextAttributes: + "order_location": "IN" + "order_type": "online" defaultCondition: transition: nextState: RejectApplication diff --git a/code-of-conduct.md b/code-of-conduct.md index ddd14b6d..97a8526a 100644 --- a/code-of-conduct.md +++ b/code-of-conduct.md @@ -1,58 +1,11 @@ -## CNCF Community Code of Conduct v1.0 +# Code of Conduct -Other languages available: -- [Chinese/中文](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/zh.md) -- [German/Deutsch](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/de.md) -- [Spanish/Español](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/es.md) -- [French/Français](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/fr.md) -- [Italian/Italiano](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/it.md) -- [Japanese/日本語](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/jp.md) -- [Korean/한국어](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/ko.md) -- [Ukrainian/Українська](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/uk.md) -- [Russian/Русский](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/ru.md) -- [Portuguese/Português](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/pt.md) -- [Arabic/العربية](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/ar.md) -- [Polish/Polski](https://github.com/cncf/foundation/blob/master/code-of-conduct-languages/pl.md) +We follow the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). -### Contributor Code of Conduct - -As contributors and maintainers of this project, and in the interest of fostering -an open and welcoming community, we pledge to respect all people who contribute -through reporting issues, posting feature requests, updating documentation, -submitting pull requests or patches, and other activities. - -We are committed to making participation in this project a harassment-free experience for -everyone, regardless of level of experience, gender, gender identity and expression, -sexual orientation, disability, personal appearance, body size, race, ethnicity, age, -religion, or nationality. - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery -* Personal attacks -* Trolling or insulting/derogatory comments -* Public or private harassment -* Publishing others' private information, such as physical or electronic addresses, - without explicit permission -* Other unethical or unprofessional conduct. - -Project maintainers have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are not -aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers -commit themselves to fairly and consistently applying these principles to every aspect -of managing this project. Project maintainers who do not follow or enforce the Code of -Conduct may be permanently removed from the project team. - -This code of conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. - -Instances of abusive, harassing, or otherwise unacceptable behavior in Kubernetes may be reported by contacting the [Kubernetes Code of Conduct Committee](https://git.k8s.io/community/committee-code-of-conduct) via conduct@kubernetes.io. For other projects, please contact a CNCF project maintainer or our mediator, Mishi Choudhary via mishi@linux.com. - -This Code of Conduct is adapted from the Contributor Covenant -(), version 1.2.0, available at - - -### CNCF Events Code of Conduct - -CNCF events are governed by the Linux Foundation [Code of Conduct](https://events.linuxfoundation.org/code-of-conduct/) available on the event page. -This is designed to be compatible with the above policy and also includes more details on responding to incidents. \ No newline at end of file + +Please contact the [CNCF Code of Conduct Committee](mailto:conduct@cncf.io) +in order to report violations of the Code of Conduct. diff --git a/pom.xml b/pom.xml index 53a2abf3..22486415 100644 --- a/pom.xml +++ b/pom.xml @@ -1,87 +1,96 @@ - - 4.0.0 + + 4.0.0 - io.serverlessworkflow - serverlessworkflow-parent - 4.0.x - pom + io.serverlessworkflow + serverlessworkflow-parent + 4.0.x + pom - Serverless Workflow :: Parent - https://serverlessworkflow.io/sdk-java/ - Java SDK for Serverless Workflow Specification - 2020 - - CNCF - https://www.cncf.io// - - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - + Serverless Workflow :: Parent + https://serverlessworkflow.io/sdk-java/ + Java SDK for Serverless Workflow Specification + 2020 + + + serverless-workflow + Serverless Workflow Specification Authors + CNCF + + + + CNCF + https://www.cncf.io// + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + scm:git:git@github.com:serverlessworkflow/sdk-java.git + scm:git:git@github.com:serverlessworkflow/sdk-java.git + https://github.com/serverlessworkflow/sdk-java + HEAD + - - scm:git:git://github.com/serverlessworkflow/sdk-java.git - scm:git:git@github.com:serverlessworkflow/sdk-java.git - https://github.com/serverlessworkflow/sdk-java - + + api + spi + validation + diagram + utils + - - - tsurdilo - Tihomir Surdilovic - - + + 1.8 + ${java.version} + ${java.version} + UTF-8 + 3.6.2 - - api - spi - validation - diagram - utils - + + 3.2.0 + 3.1.1 + 3.8.1 + 2.8.2 + 3.0.0-M2 + 3.1.2 + 2.9.1 + 3.1.0 + 3.2.0 + ${java.version} + 1.0.1 + 3.3.0 + 2.22.0 - - 1.8 - 1.8 - 1.8 - UTF-8 - 3.6.2 - 3.0.0-M2 - 8 - 3.8.1 - 2.22.0 - 2.22.0 - 3.1.1 - 2.8.2 - 1.7.25 - 2.10.3 - 2.0.1.Final - 6.0 - 5.${version.org.junit.minor} - ${version.org.junit} - 3.0.0 - 1.4.8 - 3.13.2 - 1.0.1 - 3.9 - 1.3 - 1.5.0 - 1.14.1 - 20230618 - 3.0.11.RELEASE - 8059 - 0.17.0 - 2.9 - 3.2.0 - - true - + 1.4.9 + 2.15.3 + 1.0.87 + 3.13.0 + 0.17.0 + 1.3 + 2.0.1.Final + 1.5.0 + 3.13.2 + ${version.org.junit} + 6.0 + 5.${version.org.junit.minor} + 5.6.0 + 1.7.25 + 8059 + 3.1.2.RELEASE + + + + true + - - java - true - + + java + true + + + + + + org.slf4j + slf4j-api + ${version.org.slf4j} + + + org.slf4j + jcl-over-slf4j + ${version.org.slf4j} + + + com.fasterxml.jackson.core + jackson-core + ${version.com.fasterxml.jackson} + + + com.fasterxml.jackson.core + jackson-databind + ${version.com.fasterxml.jackson} + + + com.networknt + json-schema-validator + ${version.com.networknt} + + + org.apache.commons + commons-lang3 + + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + ${version.com.fasterxml.jackson} + + + javax.validation + validation-api + ${version.javax.validation} + + + org.apache.commons + commons-lang3 + ${version.commons.lang} + + + org.thymeleaf + thymeleaf + ${version.thymeleaf} + + + net.sourceforge.plantuml + plantuml + ${version.plantuml} + + + guru.nidi + graphviz-java + ${version.graphviz} + + + + + org.junit.jupiter + junit-jupiter-api + ${version.org.junit.jupiter} + test + + + org.junit.jupiter + junit-jupiter-engine + ${version.org.junit.jupiter} + test + + + org.junit.jupiter + junit-jupiter-params + ${version.org.junit.jupiter} + test + + + org.mockito + mockito-core + ${version.org.mockito} + test + + + ch.qos.logback + logback-classic + ${version.ch.qos.logback} + test + + + org.assertj + assertj-core + ${version.org.assertj} + test + + + org.hamcrest + hamcrest-library + ${version.hamcrest} + test + + + org.skyscreamer + jsonassert + ${version.jsonassert} + test + + + - - - - org.slf4j - slf4j-api - ${version.org.slf4j} - - - org.slf4j - jcl-over-slf4j - ${version.org.slf4j} - - - com.fasterxml.jackson.core - jackson-core - ${version.com.fasterxml.jackson} - - - com.fasterxml.jackson.core - jackson-databind - ${version.com.fasterxml.jackson} - - - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - ${version.com.fasterxml.jackson} - - - javax.validation - validation-api - ${version.javax.validation} - - - org.apache.commons - commons-lang3 - ${commons.lang.version} - - - com.github.erosb - everit-json-schema - ${json.schema.validation.version} - - - commons-logging - commons-logging - - - - - org.json - json - ${version.json} - - - org.thymeleaf - thymeleaf - ${version.thymeleaf} - - - net.sourceforge.plantuml - plantuml - ${version.plantuml} - - - guru.nidi - graphviz-java - ${version.graphviz} - + + + + + org.codehaus.mojo + buildnumber-maven-plugin + ${version.buildnumber.plugin} + + + get-scm-revision + initialize + + create + + + false + false + UNKNOWN + true + + + + + + maven-compiler-plugin + ${version.compiler.plugin} + + true + true + ${maven.compiler.source} + ${maven.compiler.target} + ${maven.compiler.source} + ${maven.compiler.target} + true + + -Xlint:unchecked + + + + - - - org.junit.jupiter - junit-jupiter-api - ${version.org.junit.jupiter} - test - - - org.junit.jupiter - junit-jupiter-engine - ${version.org.junit.jupiter} - test - - - org.junit.jupiter - junit-jupiter-params - ${version.org.junit.jupiter} - test - - - org.mockito - mockito-core - ${version.org.mockito} - test - - - ch.qos.logback - logback-classic - ${version.ch.qos.logback} - test - - - org.assertj - assertj-core - ${version.org.assertj} - test - - - org.hamcrest - hamcrest-library - ${hamcrest.version} - test - - - org.skyscreamer - jsonassert - ${jsonassert.version} - test - - - + + + + org.apache.maven.plugins + maven-gpg-plugin + ${version.gpg.plugin} + + + maven-deploy-plugin + ${version.deploy.plugin} + + 10 + + + + org.apache.maven.plugins + maven-enforcer-plugin + ${version.enforcer.plugin} + + + enforce-versions + + enforce + + + + + ${version.maven} + + + ${version.jdk} + + + + + + + + org.apache.maven.plugins + maven-source-plugin + ${version.source.plugin} + + + attach-sources + + jar-no-fork + + + + + + true + + + true + + + true + + + + ${project.url} + ${java.version} + ${java.vendor} + ${os.name} + ${os.arch} + ${os.version} + ${project.scm.url} + ${project.scm.connection} + ${buildNumber} + + + + + + org.apache.maven.plugins + maven-release-plugin + ${version.release.plugin} + + clean install + true + @{project.version} + false + true + false + + + + org.jsonschema2pojo + jsonschema2pojo-maven-plugin + ${version.jsonschema2pojo-maven-plugin} + + + org.apache.maven.plugins + maven-surefire-plugin + ${version.surefire.plugin} + + -Xmx1024m -XX:+IgnoreUnrecognizedVMOptions -XX:MaxPermSize=256m + + + + org.apache.maven.plugins + maven-failsafe-plugin + ${version.failsafe.plugin} + + -Xmx1024m -XX:+IgnoreUnrecognizedVMOptions -XX:MaxPermSize=256m + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${version.checkstyle.plugin} + + + com.coveo + fmt-maven-plugin + ${version.fmt-maven-plugin} + + + org.apache.maven.plugins + maven-jar-plugin + ${version.jar.plugin} + + + true + + + true + + + true + + + + ${project.url} + ${java.version} + ${java.vendor} + ${os.name} + ${os.arch} + ${os.version} + ${project.scm.url} + ${project.scm.connection} + ${buildNumber} + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${version.javadoc.plugin} + + + + - + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + central + Central Repository + https://repo.maven.apache.org/maven2 + default + + false + + + + + + + release + - - maven-deploy-plugin - ${version.deploy.plugin} - - 10 - - - - org.apache.maven.plugins - maven-enforcer-plugin - ${version.enforcer.plugin} - - - enforce-versions - - enforce - - - - - ${version.maven} - - - ${version.jdk} - - - - - - - - maven-compiler-plugin - ${version.compiler.plugin} - - true - true - - -Xlint:unchecked - - - - - org.jsonschema2pojo - jsonschema2pojo-maven-plugin - ${version.jsonschema2pojo-maven-plugin} - - - org.apache.maven.plugins - maven-surefire-plugin - ${version.surefire.plugin} - - -Xmx1024m -XX:+IgnoreUnrecognizedVMOptions -XX:MaxPermSize=256m - - - - org.apache.maven.plugins - maven-checkstyle-plugin - ${version.checkstyle.plugin} - - - com.coveo - fmt-maven-plugin - ${version.fmt-maven-plugin} - - - org.apache.maven.plugins - maven-jar-plugin - ${version.jar.plugin} - - - org.apache.maven.plugins - maven-source-plugin - 3.0.1 - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 3.5.0 - - 8 - -Xdoclint:none - - - - attach-javadocs - - jar - - - false - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.6 - - - sign-artifacts - verify - - sign - - - - --batch - --pinentry-mode - loopback - - - - - + + org.apache.maven.plugins + maven-gpg-plugin + + + --pinentry-mode + loopback + + + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + package + + jar + + + + - - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - + + + - - - central - Central Repository - https://repo.maven.apache.org/maven2 - default - - false - - - \ No newline at end of file diff --git a/validation/pom.xml b/validation/pom.xml index 57afbbb5..6b4e808a 100644 --- a/validation/pom.xml +++ b/validation/pom.xml @@ -34,12 +34,10 @@ org.apache.commons commons-lang3 - - com.github.erosb - everit-json-schema + com.networknt + json-schema-validator - org.junit.jupiter diff --git a/validation/src/main/java/io/serverlessworkflow/validation/WorkflowValidatorImpl.java b/validation/src/main/java/io/serverlessworkflow/validation/WorkflowValidatorImpl.java index abef046b..12e4e915 100644 --- a/validation/src/main/java/io/serverlessworkflow/validation/WorkflowValidatorImpl.java +++ b/validation/src/main/java/io/serverlessworkflow/validation/WorkflowValidatorImpl.java @@ -15,8 +15,9 @@ */ package io.serverlessworkflow.validation; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.SpecVersion.VersionFlag; import io.serverlessworkflow.api.Workflow; import io.serverlessworkflow.api.actions.Action; import io.serverlessworkflow.api.events.EventDefinition; @@ -27,15 +28,14 @@ import io.serverlessworkflow.api.states.*; import io.serverlessworkflow.api.switchconditions.DataCondition; import io.serverlessworkflow.api.switchconditions.EventCondition; +import io.serverlessworkflow.api.utils.Utils; import io.serverlessworkflow.api.validation.ValidationError; import io.serverlessworkflow.api.validation.WorkflowSchemaLoader; +import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; -import org.everit.json.schema.Schema; -import org.everit.json.schema.ValidationException; -import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,7 +44,7 @@ public class WorkflowValidatorImpl implements WorkflowValidator { private static final Logger logger = LoggerFactory.getLogger(WorkflowValidatorImpl.class); private boolean schemaValidationEnabled = true; private List validationErrors = new ArrayList<>(); - private Schema workflowSchema = WorkflowSchemaLoader.getWorkflowSchema(); + private JsonNode workflowSchema = WorkflowSchemaLoader.getWorkflowSchema(); private String source; private Workflow workflow; @@ -66,34 +66,13 @@ public List validate() { if (workflow == null) { try { if (schemaValidationEnabled && source != null) { - try { - if (!source.trim().startsWith("{")) { - // convert yaml to json to validate - ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory()); - Object obj = yamlReader.readValue(source, Object.class); - - ObjectMapper jsonWriter = new ObjectMapper(); - - workflowSchema.validate(new JSONObject(jsonWriter.writeValueAsString(obj))); - } else { - workflowSchema.validate(new JSONObject(source)); - } - } catch (ValidationException e) { - e.getCausingExceptions().stream() - .map(ValidationException::getMessage) - .forEach( - m -> { - if ((!m.equals("#/functions: expected type: JSONObject, found: JSONArray") - && !m.equals("#/events: expected type: JSONObject, found: JSONArray") - && !m.equals("#/start: expected type: JSONObject, found: String") - && !m.equals("#/retries: expected type: JSONObject, found: JSONArray"))) { - addValidationError(m, ValidationError.SCHEMA_VALIDATION); - } - }); - } + JsonSchemaFactory.getInstance(VersionFlag.V7) + .getSchema(workflowSchema) + .validate(Utils.getNode(source)) + .forEach(m -> addValidationError(m.getMessage(), ValidationError.SCHEMA_VALIDATION)); } - } catch (Exception e) { - logger.error("Schema validation exception: " + e.getMessage()); + } catch (IOException e) { + logger.error("Unexpected error during validation", e); } } @@ -101,258 +80,244 @@ public List validate() { // there is no point of doing the workflow validation if (validationErrors.size() > 0) { return validationErrors; - } else { - if (workflow == null) { - workflow = Workflow.fromSource(source); - } + } else if (workflow == null) { + workflow = Workflow.fromSource(source); + } - List functions = - workflow.getFunctions() != null ? workflow.getFunctions().getFunctionDefs() : null; + List functions = + workflow.getFunctions() != null ? workflow.getFunctions().getFunctionDefs() : null; - List events = - workflow.getEvents() != null ? workflow.getEvents().getEventDefs() : null; + List events = + workflow.getEvents() != null ? workflow.getEvents().getEventDefs() : null; - if ((workflow.getId() == null || workflow.getId().trim().isEmpty()) - && (workflow.getKey() == null || workflow.getKey().trim().isEmpty())) { - addValidationError( - "Workflow id or key should not be empty", ValidationError.WORKFLOW_VALIDATION); - } + if ((workflow.getId() == null || workflow.getId().trim().isEmpty()) + && (workflow.getKey() == null || workflow.getKey().trim().isEmpty())) { + addValidationError( + "Workflow id or key should not be empty", ValidationError.WORKFLOW_VALIDATION); + } - if (workflow.getVersion() == null || workflow.getVersion().trim().isEmpty()) { - addValidationError( - "Workflow version should not be empty", ValidationError.WORKFLOW_VALIDATION); - } + if (workflow.getVersion() == null || workflow.getVersion().trim().isEmpty()) { + addValidationError( + "Workflow version should not be empty", ValidationError.WORKFLOW_VALIDATION); + } - if (workflow.getStates() == null || workflow.getStates().isEmpty()) { - addValidationError("No states found", ValidationError.WORKFLOW_VALIDATION); - } + if (workflow.getStates() == null || workflow.getStates().isEmpty()) { + addValidationError("No states found", ValidationError.WORKFLOW_VALIDATION); + } - if (workflow.getStates() != null && !workflow.getStates().isEmpty()) { - boolean existingStateWithStartProperty = false; - if (workflow.getStart() != null) { - String startProperty = workflow.getStart().getStateName(); - for (State s : workflow.getStates()) { - if (s.getName().equals(startProperty)) { - existingStateWithStartProperty = true; - break; - } + if (workflow.getStates() != null && !workflow.getStates().isEmpty()) { + boolean existingStateWithStartProperty = false; + if (workflow.getStart() != null) { + String startProperty = workflow.getStart().getStateName(); + for (State s : workflow.getStates()) { + if (s.getName().equals(startProperty)) { + existingStateWithStartProperty = true; + break; } - } else { - existingStateWithStartProperty = true; - } - if (!existingStateWithStartProperty) { - addValidationError( - "No state name found that matches the workflow start definition", - ValidationError.WORKFLOW_VALIDATION); } + } else { + existingStateWithStartProperty = true; } + if (!existingStateWithStartProperty) { + addValidationError( + "No state name found that matches the workflow start definition", + ValidationError.WORKFLOW_VALIDATION); + } + } - Validation validation = new Validation(); - if (workflow.getStates() != null && !workflow.getStates().isEmpty()) { - workflow - .getStates() - .forEach( - s -> { - if (s.getName() != null && s.getName().trim().isEmpty()) { - addValidationError( - "State name should not be empty", ValidationError.WORKFLOW_VALIDATION); - } else { - validation.addState(s.getName()); - } - - if (s.getEnd() != null) { - validation.addEndState(); - } - - if (s instanceof OperationState) { - OperationState operationState = (OperationState) s; - - List actions = operationState.getActions(); - for (Action action : actions) { - if (action.getFunctionRef() != null) { - if (action.getFunctionRef().getRefName().isEmpty()) { - addValidationError( - "Operation State action functionRef should not be null or empty", - ValidationError.WORKFLOW_VALIDATION); - } - - if (!haveFunctionDefinition( - action.getFunctionRef().getRefName(), functions)) { - addValidationError( - "Operation State action functionRef does not reference an existing workflow function definition", - ValidationError.WORKFLOW_VALIDATION); - } + Validation validation = new Validation(); + if (workflow.getStates() != null && !workflow.getStates().isEmpty()) { + workflow + .getStates() + .forEach( + s -> { + if (s.getName() != null && s.getName().trim().isEmpty()) { + addValidationError( + "State name should not be empty", ValidationError.WORKFLOW_VALIDATION); + } else { + validation.addState(s.getName()); + } + + if (s.getEnd() != null) { + validation.addEndState(); + } + + if (s instanceof OperationState) { + OperationState operationState = (OperationState) s; + + List actions = operationState.getActions(); + for (Action action : actions) { + if (action.getFunctionRef() != null) { + if (action.getFunctionRef().getRefName().isEmpty()) { + addValidationError( + "Operation State action functionRef should not be null or empty", + ValidationError.WORKFLOW_VALIDATION); } - if (action.getEventRef() != null) { - if (action.getEventRef().getTriggerEventRef().isEmpty()) { - addValidationError( - "Operation State action trigger eventRef does not reference an existing workflow event definition", - ValidationError.WORKFLOW_VALIDATION); - } - - if (action.getEventRef().getResultEventRef().isEmpty()) { - addValidationError( - "Operation State action results eventRef does not reference an existing workflow event definition", - ValidationError.WORKFLOW_VALIDATION); - } - - if (!haveEventsDefinition( - action.getEventRef().getTriggerEventRef(), events)) { - addValidationError( - "Operation State action trigger event def does not reference an existing workflow event definition", - ValidationError.WORKFLOW_VALIDATION); - } - - if (!haveEventsDefinition( - action.getEventRef().getResultEventRef(), events)) { - addValidationError( - "Operation State action results event def does not reference an existing workflow event definition", - ValidationError.WORKFLOW_VALIDATION); - } + if (!haveFunctionDefinition( + action.getFunctionRef().getRefName(), functions)) { + addValidationError( + "Operation State action functionRef does not reference an existing workflow function definition", + ValidationError.WORKFLOW_VALIDATION); } } - } - if (s instanceof EventState) { - EventState eventState = (EventState) s; - if (eventState.getOnEvents() == null || eventState.getOnEvents().size() < 1) { - addValidationError( - "Event State has no eventActions defined", - ValidationError.WORKFLOW_VALIDATION); - } - List eventsActionsList = eventState.getOnEvents(); - for (OnEvents onEvents : eventsActionsList) { + if (action.getEventRef() != null) { - List eventRefs = onEvents.getEventRefs(); - if (eventRefs == null || eventRefs.size() < 1) { + if (!haveEventsDefinition( + action.getEventRef().getTriggerEventRef(), events)) { addValidationError( - "Event State eventsActions has no event refs", + "Operation State action trigger event def does not reference an existing workflow event definition", + ValidationError.WORKFLOW_VALIDATION); + } + + if (!haveEventsDefinition(action.getEventRef().getResultEventRef(), events)) { + addValidationError( + "Operation State action results event def does not reference an existing workflow event definition", ValidationError.WORKFLOW_VALIDATION); - } else { - for (String eventRef : eventRefs) { - if (!haveEventsDefinition(eventRef, events)) { - addValidationError( - "Event State eventsActions eventRef does not match a declared workflow event definition", - ValidationError.WORKFLOW_VALIDATION); - } - } } } } + } - if (s instanceof SwitchState) { - SwitchState switchState = (SwitchState) s; - if ((switchState.getDataConditions() == null - || switchState.getDataConditions().size() < 1) - && (switchState.getEventConditions() == null - || switchState.getEventConditions().size() < 1)) { - addValidationError( - "Switch state should define either data or event conditions", - ValidationError.WORKFLOW_VALIDATION); - } + if (s instanceof EventState) { + EventState eventState = (EventState) s; + if (eventState.getOnEvents() == null || eventState.getOnEvents().size() < 1) { + addValidationError( + "Event State has no eventActions defined", + ValidationError.WORKFLOW_VALIDATION); + } + List eventsActionsList = eventState.getOnEvents(); + for (OnEvents onEvents : eventsActionsList) { - if (switchState.getDefaultCondition() == null) { + List eventRefs = onEvents.getEventRefs(); + if (eventRefs == null || eventRefs.size() < 1) { addValidationError( - "Switch state should define a default transition", + "Event State eventsActions has no event refs", ValidationError.WORKFLOW_VALIDATION); - } - - if (switchState.getEventConditions() != null - && switchState.getEventConditions().size() > 0) { - List eventConditions = switchState.getEventConditions(); - for (EventCondition ec : eventConditions) { - if (!haveEventsDefinition(ec.getEventRef(), events)) { + } else { + for (String eventRef : eventRefs) { + if (!haveEventsDefinition(eventRef, events)) { addValidationError( - "Switch state event condition eventRef does not reference a defined workflow event", + "Event State eventsActions eventRef does not match a declared workflow event definition", ValidationError.WORKFLOW_VALIDATION); } - if (ec.getEnd() != null) { - validation.addEndState(); - } } } + } + } + + if (s instanceof SwitchState) { + SwitchState switchState = (SwitchState) s; + if ((switchState.getDataConditions() == null + || switchState.getDataConditions().size() < 1) + && (switchState.getEventConditions() == null + || switchState.getEventConditions().size() < 1)) { + addValidationError( + "Switch state should define either data or event conditions", + ValidationError.WORKFLOW_VALIDATION); + } - if (switchState.getDataConditions() != null - && switchState.getDataConditions().size() > 0) { - List dataConditions = switchState.getDataConditions(); - for (DataCondition dc : dataConditions) { - if (dc.getEnd() != null) { - validation.addEndState(); - } + if (switchState.getDefaultCondition() == null) { + addValidationError( + "Switch state should define a default transition", + ValidationError.WORKFLOW_VALIDATION); + } + + if (switchState.getEventConditions() != null + && switchState.getEventConditions().size() > 0) { + List eventConditions = switchState.getEventConditions(); + for (EventCondition ec : eventConditions) { + if (!haveEventsDefinition(ec.getEventRef(), events)) { + addValidationError( + "Switch state event condition eventRef does not reference a defined workflow event", + ValidationError.WORKFLOW_VALIDATION); + } + if (ec.getEnd() != null) { + validation.addEndState(); } } } - if (s instanceof SleepState) { - SleepState sleepState = (SleepState) s; - if (sleepState.getDuration() == null || sleepState.getDuration().length() < 1) { - addValidationError( - "Sleep state should have a non-empty time delay", - ValidationError.WORKFLOW_VALIDATION); + if (switchState.getDataConditions() != null + && switchState.getDataConditions().size() > 0) { + List dataConditions = switchState.getDataConditions(); + for (DataCondition dc : dataConditions) { + if (dc.getEnd() != null) { + validation.addEndState(); + } } } + } + + if (s instanceof SleepState) { + SleepState sleepState = (SleepState) s; + if (sleepState.getDuration() == null || sleepState.getDuration().length() < 1) { + addValidationError( + "Sleep state should have a non-empty time delay", + ValidationError.WORKFLOW_VALIDATION); + } + } - if (s instanceof ParallelState) { - ParallelState parallelState = (ParallelState) s; + if (s instanceof ParallelState) { + ParallelState parallelState = (ParallelState) s; - if (parallelState.getBranches() == null - || parallelState.getBranches().size() < 2) { - addValidationError( - "Parallel state should have at lest two branches", - ValidationError.WORKFLOW_VALIDATION); - } + if (parallelState.getBranches() == null + || parallelState.getBranches().size() < 2) { + addValidationError( + "Parallel state should have at lest two branches", + ValidationError.WORKFLOW_VALIDATION); } + } - if (s instanceof InjectState) { - InjectState injectState = (InjectState) s; - if (injectState.getData() == null || injectState.getData().isEmpty()) { - addValidationError( - "InjectState should have non-null data", - ValidationError.WORKFLOW_VALIDATION); - } + if (s instanceof InjectState) { + InjectState injectState = (InjectState) s; + if (injectState.getData() == null || injectState.getData().isEmpty()) { + addValidationError( + "InjectState should have non-null data", + ValidationError.WORKFLOW_VALIDATION); } + } - if (s instanceof ForEachState) { - ForEachState forEachState = (ForEachState) s; - if (forEachState.getInputCollection() == null - || forEachState.getInputCollection().isEmpty()) { - addValidationError( - "ForEach state should have a valid inputCollection", - ValidationError.WORKFLOW_VALIDATION); - } + if (s instanceof ForEachState) { + ForEachState forEachState = (ForEachState) s; + if (forEachState.getInputCollection() == null + || forEachState.getInputCollection().isEmpty()) { + addValidationError( + "ForEach state should have a valid inputCollection", + ValidationError.WORKFLOW_VALIDATION); } + } - if (s instanceof CallbackState) { - CallbackState callbackState = (CallbackState) s; + if (s instanceof CallbackState) { + CallbackState callbackState = (CallbackState) s; - if (!haveEventsDefinition(callbackState.getEventRef(), events)) { - addValidationError( - "CallbackState event ref does not reference a defined workflow event definition", - ValidationError.WORKFLOW_VALIDATION); - } + if (!haveEventsDefinition(callbackState.getEventRef(), events)) { + addValidationError( + "CallbackState event ref does not reference a defined workflow event definition", + ValidationError.WORKFLOW_VALIDATION); + } - if (!haveFunctionDefinition( - callbackState.getAction().getFunctionRef().getRefName(), functions)) { - addValidationError( - "CallbackState action function ref does not reference a defined workflow function definition", - ValidationError.WORKFLOW_VALIDATION); - } + if (!haveFunctionDefinition( + callbackState.getAction().getFunctionRef().getRefName(), functions)) { + addValidationError( + "CallbackState action function ref does not reference a defined workflow function definition", + ValidationError.WORKFLOW_VALIDATION); } - }); + } + }); - if (validation.endStates == 0) { - addValidationError("No end state found.", ValidationError.WORKFLOW_VALIDATION); - } + if (validation.endStates == 0) { + addValidationError("No end state found.", ValidationError.WORKFLOW_VALIDATION); } - - return validationErrors; } + + return validationErrors; } @Override public boolean isValid() { - return validate().size() < 1; + return validate().isEmpty(); } @Override @@ -381,17 +346,30 @@ private boolean haveFunctionDefinition(String functionName, List events) { + if (eventName == null) { + return true; + } if (events != null) { EventDefinition eve = events.stream().filter(e -> e.getName().equals(eventName)).findFirst().orElse(null); - return eve == null ? false : true; } else { return false; } } + private static final Set skipMessages = + new HashSet() { + { + add("$.start: string found, object expected"); + add("$.functions: array found, object expected"); + } + }; + private void addValidationError(String message, String type) { + if (skipMessages.contains(message)) { + return; + } ValidationError mainError = new ValidationError(); mainError.setMessage(message); mainError.setType(type); @@ -399,30 +377,9 @@ private void addValidationError(String message, String type) { } private class Validation { - - final Set events = new HashSet<>(); - final Set functions = new HashSet<>(); final Set states = new HashSet<>(); Integer endStates = 0; - void addFunction(String name) { - if (functions.contains(name)) { - addValidationError( - "Function does not have an unique name: " + name, ValidationError.WORKFLOW_VALIDATION); - } else { - functions.add(name); - } - } - - void addEvent(String name) { - if (events.contains(name)) { - addValidationError( - "Event does not have an unique name: " + name, ValidationError.WORKFLOW_VALIDATION); - } else { - events.add(name); - } - } - void addState(String name) { if (states.contains(name)) { addValidationError( diff --git a/validation/src/test/java/io/serverlessworkflow/validation/test/WorkflowValidationTest.java b/validation/src/test/java/io/serverlessworkflow/validation/test/WorkflowValidationTest.java index a81e14f6..08237525 100644 --- a/validation/src/test/java/io/serverlessworkflow/validation/test/WorkflowValidationTest.java +++ b/validation/src/test/java/io/serverlessworkflow/validation/test/WorkflowValidationTest.java @@ -15,14 +15,26 @@ */ package io.serverlessworkflow.validation.test; +import static io.serverlessworkflow.api.states.DefaultState.Type.OPERATION; import static io.serverlessworkflow.api.states.DefaultState.Type.SLEEP; import io.serverlessworkflow.api.Workflow; +import io.serverlessworkflow.api.actions.Action; import io.serverlessworkflow.api.end.End; +import io.serverlessworkflow.api.events.EventDefinition; +import io.serverlessworkflow.api.events.EventRef; +import io.serverlessworkflow.api.functions.FunctionDefinition; +import io.serverlessworkflow.api.functions.FunctionDefinition.Type; +import io.serverlessworkflow.api.functions.FunctionRef; import io.serverlessworkflow.api.interfaces.WorkflowValidator; +import io.serverlessworkflow.api.retry.RetryDefinition; import io.serverlessworkflow.api.start.Start; +import io.serverlessworkflow.api.states.OperationState; import io.serverlessworkflow.api.states.SleepState; import io.serverlessworkflow.api.validation.ValidationError; +import io.serverlessworkflow.api.workflow.Events; +import io.serverlessworkflow.api.workflow.Functions; +import io.serverlessworkflow.api.workflow.Retries; import io.serverlessworkflow.validation.WorkflowValidatorImpl; import java.util.Arrays; import java.util.List; @@ -46,8 +58,7 @@ public void testIncompleteYamlWithSchemaValidation() { List validationErrors = workflowValidator.setSource("---\n" + "key: abc\n").validate(); Assertions.assertNotNull(validationErrors); - System.out.println(validationErrors); - Assertions.assertEquals(3, validationErrors.size()); + Assertions.assertEquals(4, validationErrors.size()); } @Test @@ -108,11 +119,10 @@ public void testWorkflowMissingStatesIdAndKey() { + "}") .validate(); Assertions.assertNotNull(validationErrors); - Assertions.assertEquals(2, validationErrors.size()); + Assertions.assertEquals(1, validationErrors.size()); Assertions.assertEquals( - "Workflow id or key should not be empty", validationErrors.get(0).getMessage()); - Assertions.assertEquals("No states found", validationErrors.get(1).getMessage()); + "$.id: is missing but it is required", validationErrors.get(0).getMessage()); } @Test @@ -162,7 +172,6 @@ public void testOperationStateNoFunctionRef() { Assertions.assertNotNull(validationErrors); Assertions.assertEquals(1, validationErrors.size()); - // validationErrors.stream().forEach(v -> System.out.println(v.toString())); Assertions.assertEquals( "Operation State action functionRef does not reference an existing workflow function definition", validationErrors.get(0).getMessage()); @@ -280,4 +289,48 @@ public void testMissingFunctionRefForCallbackState() { "CallbackState action function ref does not reference a defined workflow function definition", validationErrors.get(1).getMessage()); } + + @Test + void testFunctionCall() { + Workflow workflow = + new Workflow() + .withId("test-workflow") + .withVersion("1.0") + .withStart(new Start().withStateName("start")) + .withFunctions( + new Functions( + Arrays.asList(new FunctionDefinition("expression").withType(Type.EXPRESSION)))) + .withStates( + Arrays.asList( + new OperationState() + .withName("start") + .withType(OPERATION) + .withActions( + Arrays.asList( + new Action().withFunctionRef(new FunctionRef("expression")))) + .withEnd(new End()))); + Assertions.assertTrue(new WorkflowValidatorImpl().setWorkflow(workflow).validate().isEmpty()); + } + + @Test + void testEventCall() { + Workflow workflow = + new Workflow() + .withId("test-workflow") + .withVersion("1.0") + .withStart(new Start().withStateName("start")) + .withEvents(new Events(Arrays.asList(new EventDefinition().withName("event")))) + .withRetries(new Retries(Arrays.asList(new RetryDefinition("start", "PT1S")))) + .withStates( + Arrays.asList( + new OperationState() + .withName("start") + .withType(OPERATION) + .withActions( + Arrays.asList( + new Action() + .withEventRef(new EventRef().withTriggerEventRef("event")))) + .withEnd(new End()))); + Assertions.assertTrue(new WorkflowValidatorImpl().setWorkflow(workflow).validate().isEmpty()); + } }