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()); + } } 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