diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f23e469e..5ef5e9d5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @tsurdilo @manuelstein @ricardozanini \ No newline at end of file +* @ricardozanini @fjtirado \ No newline at end of file diff --git a/.github/OWNERS b/.github/OWNERS index da3ddc3f..0db9cb96 100644 --- a/.github/OWNERS +++ b/.github/OWNERS @@ -1,10 +1,8 @@ reviewers: - - tsurdilo - - manuelstein - ricardozanini + - fjtirado approvers: - - tsurdilo - - manuelstein - ricardozanini + - fjtirado labels: - sig/contributor-experience \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..907b5f95 --- /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 + - fjtirado \ No newline at end of file diff --git a/.github/project.yml b/.github/project.yml new file mode 100644 index 00000000..016dfafa --- /dev/null +++ b/.github/project.yml @@ -0,0 +1,3 @@ +release: + current-version: 4.2.0.Final + next-version: 4.3.0-SNAPSHOT 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..e5ed35ac 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.* pull_request: branches: - - main + - 4.* 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@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + 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..44f54117 --- /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@main + 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..2e4ec8ce --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,60 @@ +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@v4 + + - 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 17 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + 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: | + git checkout -b release + mvn -B release:prepare -Prelease -DreleaseVersion=${{steps.metadata.outputs.current-version}} -DdevelopmentVersion=${{steps.metadata.outputs.next-version}} + cat release.properties + git checkout ${{github.base_ref}} + git rebase release + mvn -B release:perform -Prelease -Darguments="-DperformRelease" + env: + MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} + + - name: Push tags + run: git push && git push --tags \ No newline at end of file diff --git a/.gitignore b/.gitignore index d4dfde66..1dfd7048 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.DS_Store HELP.md target/ !.mvn/wrapper/maven-wrapper.jar diff --git a/README.md b/README.md index efb425dc..7aef4f23 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ to parse and validate workflow definitions as well as generate the workflow diag ### Status -| Latest Releases | Conformance to spec version | -| :---: | :---: | -| 4.0.0-SNAPSHOT (main branch) | [v0.8](https://github.com/serverlessworkflow/specification/tree/0.8.x) | +| Latest Releases | Conformance to spec version | +|:-----------------------------------------------------------------------:| :---: | +| [4.1.0.Final](https://github.com/serverlessworkflow/sdk-java/releases/) | [v0.8](https://github.com/serverlessworkflow/specification/tree/0.8.x) | | [3.0.0.Final](https://github.com/serverlessworkflow/sdk-java/releases/) | [v0.7](https://github.com/serverlessworkflow/specification/tree/0.7.x) | | [2.0.0.Final](https://github.com/serverlessworkflow/sdk-java/releases/) | [v0.6](https://github.com/serverlessworkflow/specification/tree/0.6.x) | | [1.0.3.Final](https://github.com/serverlessworkflow/sdk-java/releases/) | [v0.5](https://github.com/serverlessworkflow/specification/tree/0.5.x) | @@ -64,31 +64,31 @@ b) Add the following dependencies to your pom.xml `dependencies` section: io.serverlessworkflow serverlessworkflow-api - 4.0.0-SNAPSHOT + 4.1.0.Final io.serverlessworkflow serverlessworkflow-spi - 4.0.0-SNAPSHOT + 4.1.0.Final io.serverlessworkflow serverlessworkflow-validation - 4.0.0-SNAPSHOT + 4.1.0.Final io.serverlessworkflow serverlessworkflow-diagram - 4.0.0-SNAPSHOT + 4.1.0.Final io.serverlessworkflow serverlessworkflow-util - 4.0.0-SNAPSHOT + 4.1.0.Final ``` @@ -103,11 +103,11 @@ maven { url "https://oss.sonatype.org/content/repositories/snapshots" } b) Add the following dependencies to your build.gradle `dependencies` section: ```text -implementation("io.serverlessworkflow:serverlessworkflow-api:4.0.0-SNAPSHOT") -implementation("io.serverlessworkflow:serverlessworkflow-spi:4.0.0-SNAPSHOT") -implementation("io.serverlessworkflow:serverlessworkflow-validation:4.0.0-SNAPSHOT") -implementation("io.serverlessworkflow:serverlessworkflow-diagram:4.0.0-SNAPSHOT") -implementation("io.serverlessworkflow:serverlessworkflow-util:4.0.0-SNAPSHOT") +implementation("io.serverlessworkflow:serverlessworkflow-api:4.1.0.Final") +implementation("io.serverlessworkflow:serverlessworkflow-spi:4.1.0.Final") +implementation("io.serverlessworkflow:serverlessworkflow-validation:4.1.0.Final") +implementation("io.serverlessworkflow:serverlessworkflow-diagram:4.1.0.Final") +implementation("io.serverlessworkflow:serverlessworkflow-util:4.1.0.Final") ``` ### How to Use @@ -267,6 +267,22 @@ String diagramSVG = workflowDiagram.getSvgDiagram(); `diagramSVG` includes the diagram SVG source which you can then decide to save to a file, print, or process further. +In case default visualization of the workflow is not sufficient you can provide custom workflow template to be +used while generating the SVG file. Easiest is to start off from the default template and customize it to your needs. + +Custom template must be on the classpath in `templates/plantuml` directory and must use `.txt` extension. Next +template is set on `WorkflowDiagram` instance as shown below. + +``` java +Workflow workflow = Workflow.fromSource(source); + +WorkflowDiagram workflowDiagram = new WorkflowDiagramImpl(); +workflowDiagram.setWorkflow(workflow); +workflowDiagram.setTemplate("custom-template"); + +String diagramSVG = workflowDiagram.getSvgDiagram(); +``` + By default the diagram legend is now shown. If you want to enable it you can do: ``` java @@ -296,15 +312,18 @@ Here are some generated diagrams from the specification examples (with legend en Workflow utils provide a number of useful methods for extracting information from workflow definitions. Once you have a `Workflow` instance, you can use it ##### Get Starting State -```Java + +```java State startingState = WorkflowUtils.getStartingState(workflow); ``` ##### Get States by State Type -```Java + +```java List states = WorkflowUtils.getStates(workflow, DefaultState.Type.EVENT); ``` ##### Get Consumed-Events, Produced-Events and their count -```Java + +```java List consumedEvents = WorkflowUtils.getWorkflowConsumedEvents(workflow); int consumedEventsCount = WorkflowUtils.getWorkflowConsumedEventsCount(workflow); @@ -312,7 +331,8 @@ State startingState = WorkflowUtils.getStartingState(workflow); int producedEventsCount = WorkflowUtils.getWorkflowProducedEventsCount(workflow); ``` ##### Get Defined Consumed-Events, Defined Produced-Events and their count -```Java + +```java List consumedEvents = WorkflowUtils.getWorkflowConsumedEventsCount(workflow); int consumedEventsCount = WorkflowUtils.getWorkflowConsumedEventsCount(workflow); @@ -320,12 +340,14 @@ State startingState = WorkflowUtils.getStartingState(workflow); int producedEventsCount = WorkflowUtils.getWorkflowProducedEventsCount(workflow); ``` ##### Get Function definitions which is used by an action -```Java + +```java FunctionDefinition finalizeApplicationFunctionDefinition = WorkflowUtils.getFunctionDefinitionsForAction(workflow, "finalizeApplicationAction"); ``` ##### Get Actions which uses a Function definition -```Java + +```java List actionsForFunctionDefinition = WorkflowUtils.getActionsForFunctionDefinition(workflow, functionRefName); ``` \ No newline at end of file diff --git a/api/pom.xml b/api/pom.xml index b20aaf22..c2d52765 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -1,17 +1,14 @@ - + 4.0.0 io.serverlessworkflow serverlessworkflow-parent - 4.0.0-SNAPSHOT + 4.3.0-SNAPSHOT serverlessworkflow-api Serverless Workflow :: API - ${project.parent.version} jar Java SDK for Serverless Workflow Specification @@ -27,31 +24,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 + jakarta.validation + jakarta.validation-api - - org.json - json - - - com.github.erosb - everit-json-schema - - org.junit.jupiter @@ -98,12 +83,13 @@ true true false + true false false true true true - 1.8 + ${java.version} true @@ -122,13 +108,13 @@ - - + + - - + + @@ -156,21 +142,12 @@ - com.coveo - fmt-maven-plugin - - src/main/java - src/test/java - false - .*\.java - false - false - - + org.apache.maven.plugins + maven-jar-plugin - format + test-jar diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/AuthDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/AuthDeserializer.java new file mode 100644 index 00000000..aa078cb4 --- /dev/null +++ b/api/src/main/java/io/serverlessworkflow/api/deserializers/AuthDeserializer.java @@ -0,0 +1,88 @@ +/* + * 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.deserializers; + +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 io.serverlessworkflow.api.auth.AuthDefinition; +import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; +import io.serverlessworkflow.api.utils.Utils; +import io.serverlessworkflow.api.workflow.Auth; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AuthDeserializer extends StdDeserializer { + + private static final long serialVersionUID = 520L; + private static Logger logger = LoggerFactory.getLogger(AuthDeserializer.class); + + @SuppressWarnings("unused") + private WorkflowPropertySource context; + + public AuthDeserializer() { + this(Auth.class); + } + + public AuthDeserializer(Class vc) { + super(vc); + } + + public AuthDeserializer(WorkflowPropertySource context) { + this(Auth.class); + this.context = context; + } + + @Override + public Auth deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + + ObjectMapper mapper = (ObjectMapper) jp.getCodec(); + JsonNode node = jp.getCodec().readTree(jp); + + Auth auth = new Auth(); + List authDefinitions = new ArrayList<>(); + + if (node.isArray()) { + for (final JsonNode nodeEle : node) { + authDefinitions.add(mapper.treeToValue(nodeEle, AuthDefinition.class)); + } + } else { + String authFileDef = node.asText(); + String authFileSrc = Utils.getResourceFileAsString(authFileDef); + if (authFileSrc != null && authFileSrc.trim().length() > 0) { + JsonNode authRefNode = Utils.getNode(authFileSrc); + JsonNode refAuth = authRefNode.get("auth"); + if (refAuth != null) { + for (final JsonNode nodeEle : refAuth) { + authDefinitions.add(mapper.treeToValue(nodeEle, AuthDefinition.class)); + } + } else { + logger.error("Unable to find auth definitions in reference file: {}", authFileSrc); + } + + } else { + logger.error("Unable to load auth defs reference file: {}", authFileSrc); + } + } + auth.setAuthDefs(authDefinitions); + return auth; + } +} 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/interfaces/WorkflowDiagram.java b/api/src/main/java/io/serverlessworkflow/api/interfaces/WorkflowDiagram.java index 6bba312c..0c62d4d2 100644 --- a/api/src/main/java/io/serverlessworkflow/api/interfaces/WorkflowDiagram.java +++ b/api/src/main/java/io/serverlessworkflow/api/interfaces/WorkflowDiagram.java @@ -22,6 +22,8 @@ public interface WorkflowDiagram { WorkflowDiagram setSource(String source); + WorkflowDiagram setTemplate(String template); + String getSvgDiagram() throws Exception; WorkflowDiagram showLegend(boolean showLegend); 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/WorkflowModule.java b/api/src/main/java/io/serverlessworkflow/api/mapper/WorkflowModule.java index 1486d32a..5b42598d 100644 --- a/api/src/main/java/io/serverlessworkflow/api/mapper/WorkflowModule.java +++ b/api/src/main/java/io/serverlessworkflow/api/mapper/WorkflowModule.java @@ -42,11 +42,11 @@ public class WorkflowModule extends SimpleModule { - private static final long serialVersionUID = 510l; + private static final long serialVersionUID = 510L; - private WorkflowPropertySource workflowPropertySource; - private ExtensionSerializer extensionSerializer; - private ExtensionDeserializer extensionDeserializer; + private final WorkflowPropertySource workflowPropertySource; + private final ExtensionSerializer extensionSerializer; + private final ExtensionDeserializer extensionDeserializer; public WorkflowModule() { this(null); @@ -121,6 +121,7 @@ private void addDefaultDeserializers() { StateExecTimeout.class, new StateExecTimeoutDeserializer(workflowPropertySource)); addDeserializer(Errors.class, new ErrorsDeserializer(workflowPropertySource)); addDeserializer(ContinueAs.class, new ContinueAsDeserializer(workflowPropertySource)); + addDeserializer(Auth.class, new AuthDeserializer(workflowPropertySource)); } public ExtensionSerializer getExtensionSerializer() { 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/serializers/FunctionRefSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/FunctionRefSerializer.java index cf5ce81f..71f61606 100644 --- a/api/src/main/java/io/serverlessworkflow/api/serializers/FunctionRefSerializer.java +++ b/api/src/main/java/io/serverlessworkflow/api/serializers/FunctionRefSerializer.java @@ -41,12 +41,12 @@ public void serialize(FunctionRef functionRef, JsonGenerator gen, SerializerProv && (functionRef.getInvoke() == null || functionRef.getInvoke().equals(FunctionRef.Invoke.SYNC)) && functionRef.getRefName() != null - && functionRef.getRefName().length() > 0) { + && !functionRef.getRefName().isEmpty()) { gen.writeString(functionRef.getRefName()); } else { gen.writeStartObject(); - if (functionRef.getRefName() != null && functionRef.getRefName().length() > 0) { + if (functionRef.getRefName() != null && !functionRef.getRefName().isEmpty()) { gen.writeStringField("refName", functionRef.getRefName()); } @@ -54,7 +54,7 @@ public void serialize(FunctionRef functionRef, JsonGenerator gen, SerializerProv gen.writeObjectField("arguments", functionRef.getArguments()); } - if (functionRef.getSelectionSet() != null && functionRef.getSelectionSet().length() > 0) { + if (functionRef.getSelectionSet() != null && !functionRef.getSelectionSet().isEmpty()) { gen.writeStringField("selectionSet", functionRef.getSelectionSet()); } diff --git a/api/src/main/java/io/serverlessworkflow/api/serializers/WorkflowSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/WorkflowSerializer.java index b45871fa..b5ac7cbb 100644 --- a/api/src/main/java/io/serverlessworkflow/api/serializers/WorkflowSerializer.java +++ b/api/src/main/java/io/serverlessworkflow/api/serializers/WorkflowSerializer.java @@ -15,198 +15,171 @@ */ package io.serverlessworkflow.api.serializers; +import java.io.IOException; +import java.security.MessageDigest; +import java.util.List; +import java.util.UUID; + import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.ser.std.StdSerializer; import io.serverlessworkflow.api.Workflow; -import io.serverlessworkflow.api.error.ErrorDefinition; -import io.serverlessworkflow.api.events.EventDefinition; -import io.serverlessworkflow.api.functions.FunctionDefinition; import io.serverlessworkflow.api.interfaces.Extension; import io.serverlessworkflow.api.interfaces.State; -import io.serverlessworkflow.api.retry.RetryDefinition; -import java.io.IOException; -import java.security.MessageDigest; -import java.util.UUID; public class WorkflowSerializer extends StdSerializer { - public WorkflowSerializer() { - this(Workflow.class); - } - - protected WorkflowSerializer(Class t) { - super(t); - } - - private static final char[] hexArray = "0123456789ABCDEF".toCharArray(); - - @Override - public void serialize(Workflow workflow, JsonGenerator gen, SerializerProvider provider) - throws IOException { - - gen.writeStartObject(); - - if (workflow.getId() != null && !workflow.getId().isEmpty()) { - gen.writeStringField("id", workflow.getId()); - } else { - gen.writeStringField("id", generateUniqueId()); - } - - gen.writeStringField("name", workflow.getName()); - - if (workflow.getDescription() != null && !workflow.getDescription().isEmpty()) { - gen.writeStringField("description", workflow.getDescription()); - } - - if (workflow.getVersion() != null && !workflow.getVersion().isEmpty()) { - gen.writeStringField("version", workflow.getVersion()); - } - - if (workflow.getDataInputSchema() != null) { - if (workflow.getDataInputSchema().getSchema() != null - && workflow.getDataInputSchema().getSchema().length() > 0 - && workflow.getDataInputSchema().isFailOnValidationErrors()) { - gen.writeStringField("dataInputSchema", workflow.getDataInputSchema().getSchema()); - - } else if (workflow.getDataInputSchema().getSchema() != null - && workflow.getDataInputSchema().getSchema().length() > 0 - && !workflow.getDataInputSchema().isFailOnValidationErrors()) { - gen.writeObjectField("dataInputSchema", workflow.getDataInputSchema()); - } - } - - if (workflow.getStart() != null) { - gen.writeObjectField("start", workflow.getStart()); - } - - if (workflow.getSpecVersion() != null && !workflow.getSpecVersion().isEmpty()) { - gen.writeStringField("specVersion", workflow.getSpecVersion()); - } - - if (workflow.getExtensions() != null && !workflow.getExpressionLang().isEmpty()) { - gen.writeStringField("expressionLang", workflow.getExpressionLang()); - } - - if (workflow.isKeepActive()) { - gen.writeBooleanField("keepActive", workflow.isKeepActive()); - } - - if (workflow.isAutoRetries()) { - gen.writeBooleanField("autoRetries", workflow.isAutoRetries()); - } - - if (workflow.getMetadata() != null && !workflow.getMetadata().isEmpty()) { - gen.writeObjectField("metadata", workflow.getMetadata()); - } - - if (workflow.getEvents() != null && !workflow.getEvents().getEventDefs().isEmpty()) { - gen.writeArrayFieldStart("events"); - for (EventDefinition eventDefinition : workflow.getEvents().getEventDefs()) { - gen.writeObject(eventDefinition); - } - gen.writeEndArray(); - } else { - gen.writeArrayFieldStart("events"); - gen.writeEndArray(); - } - - if (workflow.getFunctions() != null && !workflow.getFunctions().getFunctionDefs().isEmpty()) { - gen.writeArrayFieldStart("functions"); - for (FunctionDefinition function : workflow.getFunctions().getFunctionDefs()) { - gen.writeObject(function); - } - gen.writeEndArray(); - } else { - gen.writeArrayFieldStart("functions"); - gen.writeEndArray(); - } - - if (workflow.getRetries() != null && !workflow.getRetries().getRetryDefs().isEmpty()) { - gen.writeArrayFieldStart("retries"); - for (RetryDefinition retry : workflow.getRetries().getRetryDefs()) { - gen.writeObject(retry); - } - gen.writeEndArray(); - } else { - gen.writeArrayFieldStart("retries"); - gen.writeEndArray(); - } - - if (workflow.getErrors() != null && !workflow.getErrors().getErrorDefs().isEmpty()) { - gen.writeArrayFieldStart("errors"); - for (ErrorDefinition error : workflow.getErrors().getErrorDefs()) { - gen.writeObject(error); - } - gen.writeEndArray(); - } else { - gen.writeArrayFieldStart("errors"); - gen.writeEndArray(); - } - - if (workflow.getSecrets() != null && !workflow.getSecrets().getSecretDefs().isEmpty()) { - gen.writeArrayFieldStart("secrets"); - for (String secretDef : workflow.getSecrets().getSecretDefs()) { - gen.writeString(secretDef); - } - gen.writeEndArray(); - } else { - gen.writeArrayFieldStart("secrets"); - gen.writeEndArray(); - } - - if (workflow.getConstants() != null && !workflow.getConstants().getConstantsDef().isEmpty()) { - gen.writeObjectField("constants", workflow.getConstants().getConstantsDef()); - } - - if (workflow.getTimeouts() != null) { - gen.writeObjectField("timeouts", workflow.getTimeouts()); - } - - if (workflow.getAuth() != null) { - gen.writeObjectField("auth", workflow.getAuth()); - } - - if (workflow.getStates() != null && !workflow.getStates().isEmpty()) { - gen.writeArrayFieldStart("states"); - for (State state : workflow.getStates()) { - gen.writeObject(state); - } - gen.writeEndArray(); - } else { - gen.writeArrayFieldStart("states"); - gen.writeEndArray(); - } - - if (workflow.getExtensions() != null && !workflow.getExtensions().isEmpty()) { - gen.writeArrayFieldStart("extensions"); - for (Extension extension : workflow.getExtensions()) { - gen.writeObject(extension); - } - gen.writeEndArray(); - } - - gen.writeEndObject(); - } - - protected static String generateUniqueId() { - try { - MessageDigest salt = MessageDigest.getInstance("SHA-256"); - - salt.update(UUID.randomUUID().toString().getBytes("UTF-8")); - return bytesToHex(salt.digest()); - } catch (Exception e) { - return UUID.randomUUID().toString(); - } - } - - protected static String bytesToHex(byte[] bytes) { - char[] hexChars = new char[bytes.length * 2]; - for (int j = 0; j < bytes.length; j++) { - int v = bytes[j] & 0xFF; - hexChars[j * 2] = hexArray[v >>> 4]; - hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + private static final char[] hexArray = "0123456789ABCDEF".toCharArray(); + + public WorkflowSerializer() { + this(Workflow.class); + } + + protected WorkflowSerializer(Class t) { + super(t); + } + + protected static String generateUniqueId() { + try { + MessageDigest salt = MessageDigest.getInstance("SHA-256"); + salt.update(UUID.randomUUID().toString().getBytes("UTF-8")); + return bytesToHex(salt.digest()); + } catch (Exception e) { + return UUID.randomUUID().toString(); + } + } + + protected static String bytesToHex(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + + // Helper to write either an array of items or a single string reference + private void writeListOrRef( + JsonGenerator gen, + SerializerProvider prov, + String fieldName, + List items, + String refValue) throws IOException { + if (items != null && !items.isEmpty()) { + gen.writeArrayFieldStart(fieldName); + for (T item : items) { + prov.defaultSerializeValue(item, gen); + } + gen.writeEndArray(); + } else if (refValue != null) { + gen.writeStringField(fieldName, refValue); + } + } + + @Override + public void serialize(Workflow workflow, JsonGenerator gen, SerializerProvider provider) + throws IOException { + + gen.writeStartObject(); + + // --- ID / key / basic fields --- + if (workflow.getId() != null && !workflow.getId().isEmpty()) { + gen.writeStringField("id", workflow.getId()); + } else { + gen.writeStringField("id", generateUniqueId()); + } + if (workflow.getKey() != null) { + gen.writeStringField("key", workflow.getKey()); + } + gen.writeStringField("name", workflow.getName()); + if (workflow.getDescription() != null && !workflow.getDescription().isEmpty()) { + gen.writeStringField("description", workflow.getDescription()); + } + if (workflow.getVersion() != null && !workflow.getVersion().isEmpty()) { + gen.writeStringField("version", workflow.getVersion()); + } + if (workflow.getAnnotations() != null && !workflow.getAnnotations().isEmpty()) { + gen.writeObjectField("annotations", workflow.getAnnotations()); + } + if (workflow.getDataInputSchema() != null) { + if (workflow.getDataInputSchema().getSchema() != null + && !workflow.getDataInputSchema().getSchema().isEmpty() + && workflow.getDataInputSchema().isFailOnValidationErrors()) { + gen.writeStringField("dataInputSchema", workflow.getDataInputSchema().getSchema()); + } else if (workflow.getDataInputSchema().getSchema() != null + && !workflow.getDataInputSchema().getSchema().isEmpty() + && !workflow.getDataInputSchema().isFailOnValidationErrors()) { + gen.writeObjectField("dataInputSchema", workflow.getDataInputSchema()); + } + } + if (workflow.getStart() != null) { + gen.writeObjectField("start", workflow.getStart()); + } + if (workflow.getSpecVersion() != null && !workflow.getSpecVersion().isEmpty()) { + gen.writeStringField("specVersion", workflow.getSpecVersion()); + } + if (workflow.getExpressionLang() != null && !workflow.getExpressionLang().isEmpty()) { + gen.writeStringField("expressionLang", workflow.getExpressionLang()); + } + if (workflow.isKeepActive()) { + gen.writeBooleanField("keepActive", workflow.isKeepActive()); + } + if (workflow.isAutoRetries()) { + gen.writeBooleanField("autoRetries", workflow.isAutoRetries()); + } + if (workflow.getMetadata() != null && !workflow.getMetadata().isEmpty()) { + gen.writeObjectField("metadata", workflow.getMetadata()); + } + + // --- Collections or references --- + if (workflow.getEvents() != null) { + writeListOrRef(gen, provider, + "events", + workflow.getEvents().getEventDefs(), + workflow.getEvents().getRefValue()); + } + if (workflow.getFunctions() != null) { + writeListOrRef(gen, provider, + "functions", + workflow.getFunctions().getFunctionDefs(), + workflow.getFunctions().getRefValue()); + } + if (workflow.getRetries() != null) { + writeListOrRef(gen, provider, + "retries", + workflow.getRetries().getRetryDefs(), + workflow.getRetries().getRefValue()); + } + if (workflow.getErrors() != null) { + writeListOrRef(gen, provider, + "errors", + workflow.getErrors().getErrorDefs(), + workflow.getErrors().getRefValue()); + } + if (workflow.getSecrets() != null) { + writeListOrRef(gen, provider, + "secrets", + workflow.getSecrets().getSecretDefs(), + workflow.getSecrets().getRefValue()); + } + + // --- Always-array fields --- + if (workflow.getStates() != null && !workflow.getStates().isEmpty()) { + gen.writeArrayFieldStart("states"); + for (State state : workflow.getStates()) { + gen.writeObject(state); + } + gen.writeEndArray(); + } + if (workflow.getExtensions() != null && !workflow.getExtensions().isEmpty()) { + gen.writeArrayFieldStart("extensions"); + for (Extension ext : workflow.getExtensions()) { + gen.writeObject(ext); + } + gen.writeEndArray(); + } + + gen.writeEndObject(); } - return new String(hexChars); - } } 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/java/io/serverlessworkflow/api/workflow/Auth.java b/api/src/main/java/io/serverlessworkflow/api/workflow/Auth.java new file mode 100644 index 00000000..e7828370 --- /dev/null +++ b/api/src/main/java/io/serverlessworkflow/api/workflow/Auth.java @@ -0,0 +1,63 @@ +/* + * Copyright 2022-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.workflow; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import io.serverlessworkflow.api.auth.AuthDefinition; + +public class Auth { + private String refValue; + private List authDefs; + + public Auth() { + } + + public Auth(AuthDefinition authDef) { + this.authDefs = new ArrayList<>(); + this.authDefs.add(authDef); + } + + public Auth(List authDefs) { + this.authDefs = authDefs; + } + + public Auth(String refValue) { + this.refValue = refValue; + } + + public String getRefValue() { + return refValue; + } + + public void setRefValue(String refValue) { + this.refValue = refValue; + } + + public List getAuthDefs() { + if (authDefs == null) { + return Collections.emptyList(); + } + return authDefs; + } + + public void setAuthDefs(List authDefs) { + this.authDefs = authDefs; + } +} diff --git a/api/src/main/java/io/serverlessworkflow/api/workflow/BaseWorkflow.java b/api/src/main/java/io/serverlessworkflow/api/workflow/BaseWorkflow.java index 61692caf..e3fc3d55 100644 --- a/api/src/main/java/io/serverlessworkflow/api/workflow/BaseWorkflow.java +++ b/api/src/main/java/io/serverlessworkflow/api/workflow/BaseWorkflow.java @@ -15,6 +15,9 @@ */ package io.serverlessworkflow.api.workflow; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; @@ -23,53 +26,53 @@ import io.serverlessworkflow.api.Workflow; import io.serverlessworkflow.api.mapper.JsonObjectMapper; import io.serverlessworkflow.api.mapper.YamlObjectMapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -/** Base Workflow provides some extra functionality for the Workflow types */ +/** + * Base Workflow provides some extra functionality for the Workflow types + */ public class BaseWorkflow { - private static JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(); - private static YamlObjectMapper yamlObjectMapper = new YamlObjectMapper(); + private static JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(); + private static YamlObjectMapper yamlObjectMapper = new YamlObjectMapper(); - private static Logger logger = LoggerFactory.getLogger(BaseWorkflow.class); + private static Logger logger = LoggerFactory.getLogger(BaseWorkflow.class); - public static Workflow fromSource(String source) { - // try it as json markup first, if fails try yaml - try { - return jsonObjectMapper.readValue(source, Workflow.class); - } catch (Exception e) { - logger.info("Unable to convert as json markup, trying as yaml"); - try { - return yamlObjectMapper.readValue(source, Workflow.class); - } catch (Exception ee) { - throw new IllegalArgumentException( - "Could not convert markup to Workflow: " + ee.getMessage()); - } + public static Workflow fromSource(String source) { + // try it as json markup first, if fails try yaml + try { + return jsonObjectMapper.readValue(source, Workflow.class); + } catch (Exception e) { + logger.info("Unable to convert as json markup, trying as yaml"); + try { + return yamlObjectMapper.readValue(source, Workflow.class); + } catch (Exception ee) { + throw new IllegalArgumentException( + "Could not convert markup to Workflow: " + ee.getMessage()); + } + } } - } - public static String toJson(Workflow workflow) { - try { - return jsonObjectMapper.writeValueAsString(workflow); - } catch (JsonProcessingException e) { - logger.error("Error mapping to json: " + e.getMessage()); - return null; + public static String toJson(Workflow workflow) { + try { + return jsonObjectMapper.writeValueAsString(workflow); + } catch (JsonProcessingException e) { + logger.error("Error mapping to json: {}", e.getMessage()); + throw new IllegalArgumentException("Could not convert workflow to json: " + e.getMessage()); + } } - } - public static String toYaml(Workflow workflow) { - try { - String jsonString = jsonObjectMapper.writeValueAsString(workflow); - JsonNode jsonNode = jsonObjectMapper.readTree(jsonString); - YAMLFactory yamlFactory = - new YAMLFactory() - .disable(YAMLGenerator.Feature.MINIMIZE_QUOTES) - .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER); - return new YAMLMapper(yamlFactory).writeValueAsString(jsonNode); - } catch (Exception e) { - logger.error("Error mapping to yaml: " + e.getMessage()); - return null; + public static String toYaml(Workflow workflow) { + try { + String jsonString = jsonObjectMapper.writeValueAsString(workflow); + JsonNode jsonNode = jsonObjectMapper.readTree(jsonString); + YAMLFactory yamlFactory = + new YAMLFactory() + .disable(YAMLGenerator.Feature.MINIMIZE_QUOTES) + .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER); + return new YAMLMapper(yamlFactory).writeValueAsString(jsonNode); + } catch (Exception e) { + logger.error("Error mapping to yaml: {}", e.getMessage()); + throw new IllegalArgumentException("Could not convert workflow to yaml: " + e.getMessage()); + } } - } } diff --git a/api/src/main/java/io/serverlessworkflow/api/workflow/Errors.java b/api/src/main/java/io/serverlessworkflow/api/workflow/Errors.java index 8431b94a..9e474139 100644 --- a/api/src/main/java/io/serverlessworkflow/api/workflow/Errors.java +++ b/api/src/main/java/io/serverlessworkflow/api/workflow/Errors.java @@ -15,36 +15,42 @@ */ package io.serverlessworkflow.api.workflow; -import io.serverlessworkflow.api.error.ErrorDefinition; +import java.util.Collections; import java.util.List; -public class Errors { - private String refValue; - private List errorDefs; - - public Errors() {} - - public Errors(List errorDefs) { - this.errorDefs = errorDefs; - } - - public Errors(String refValue) { - this.refValue = refValue; - } - - public String getRefValue() { - return refValue; - } - - public void setRefValue(String refValue) { - this.refValue = refValue; - } - - public List getErrorDefs() { - return errorDefs; - } +import io.serverlessworkflow.api.error.ErrorDefinition; - public void setErrorDefs(List errorDefs) { - this.errorDefs = errorDefs; - } +public class Errors { + private String refValue; + private List errorDefs; + + public Errors() { + } + + public Errors(List errorDefs) { + this.errorDefs = errorDefs; + } + + public Errors(String refValue) { + this.refValue = refValue; + } + + public String getRefValue() { + return refValue; + } + + public void setRefValue(String refValue) { + this.refValue = refValue; + } + + public List getErrorDefs() { + if (errorDefs == null) { + return Collections.emptyList(); + } + return errorDefs; + } + + public void setErrorDefs(List errorDefs) { + this.errorDefs = errorDefs; + } } diff --git a/api/src/main/java/io/serverlessworkflow/api/workflow/Events.java b/api/src/main/java/io/serverlessworkflow/api/workflow/Events.java index 24080e51..59af2c3f 100644 --- a/api/src/main/java/io/serverlessworkflow/api/workflow/Events.java +++ b/api/src/main/java/io/serverlessworkflow/api/workflow/Events.java @@ -15,36 +15,42 @@ */ package io.serverlessworkflow.api.workflow; -import io.serverlessworkflow.api.events.EventDefinition; +import java.util.Collections; import java.util.List; -public class Events { - private String refValue; - private List eventDefs; - - public Events() {} - - public Events(List eventDefs) { - this.eventDefs = eventDefs; - } - - public Events(String refValue) { - this.refValue = refValue; - } - - public String getRefValue() { - return refValue; - } - - public void setRefValue(String refValue) { - this.refValue = refValue; - } - - public List getEventDefs() { - return eventDefs; - } +import io.serverlessworkflow.api.events.EventDefinition; - public void setEventDefs(List eventDefs) { - this.eventDefs = eventDefs; - } +public class Events { + private String refValue; + private List eventDefs; + + public Events() { + } + + public Events(List eventDefs) { + this.eventDefs = eventDefs; + } + + public Events(String refValue) { + this.refValue = refValue; + } + + public String getRefValue() { + return refValue; + } + + public void setRefValue(String refValue) { + this.refValue = refValue; + } + + public List getEventDefs() { + if (eventDefs == null) { + return Collections.emptyList(); + } + return eventDefs; + } + + public void setEventDefs(List eventDefs) { + this.eventDefs = eventDefs; + } } diff --git a/api/src/main/java/io/serverlessworkflow/api/workflow/Functions.java b/api/src/main/java/io/serverlessworkflow/api/workflow/Functions.java index f269bc08..6e927f9b 100644 --- a/api/src/main/java/io/serverlessworkflow/api/workflow/Functions.java +++ b/api/src/main/java/io/serverlessworkflow/api/workflow/Functions.java @@ -15,36 +15,42 @@ */ package io.serverlessworkflow.api.workflow; -import io.serverlessworkflow.api.functions.FunctionDefinition; +import java.util.Collections; import java.util.List; -public class Functions { - private String refValue; - private List functionDefs; - - public Functions() {} - - public Functions(List functionDefs) { - this.functionDefs = functionDefs; - } - - public Functions(String refValue) { - this.refValue = refValue; - } - - public String getRefValue() { - return refValue; - } - - public void setRefValue(String refValue) { - this.refValue = refValue; - } - - public List getFunctionDefs() { - return functionDefs; - } +import io.serverlessworkflow.api.functions.FunctionDefinition; - public void setFunctionDefs(List functionDefs) { - this.functionDefs = functionDefs; - } +public class Functions { + private String refValue; + private List functionDefs; + + public Functions() { + } + + public Functions(List functionDefs) { + this.functionDefs = functionDefs; + } + + public Functions(String refValue) { + this.refValue = refValue; + } + + public String getRefValue() { + return refValue; + } + + public void setRefValue(String refValue) { + this.refValue = refValue; + } + + public List getFunctionDefs() { + if (functionDefs == null) { + return Collections.emptyList(); + } + return functionDefs; + } + + public void setFunctionDefs(List functionDefs) { + this.functionDefs = functionDefs; + } } diff --git a/api/src/main/java/io/serverlessworkflow/api/workflow/Retries.java b/api/src/main/java/io/serverlessworkflow/api/workflow/Retries.java index af1ae1e0..fa539ac0 100644 --- a/api/src/main/java/io/serverlessworkflow/api/workflow/Retries.java +++ b/api/src/main/java/io/serverlessworkflow/api/workflow/Retries.java @@ -15,36 +15,42 @@ */ package io.serverlessworkflow.api.workflow; -import io.serverlessworkflow.api.retry.RetryDefinition; +import java.util.Collections; import java.util.List; -public class Retries { - private String refValue; - private List retryDefs; - - public Retries() {} - - public Retries(List retryDefs) { - this.retryDefs = retryDefs; - } - - public Retries(String refValue) { - this.refValue = refValue; - } - - public String getRefValue() { - return refValue; - } - - public void setRefValue(String refValue) { - this.refValue = refValue; - } - - public List getRetryDefs() { - return retryDefs; - } +import io.serverlessworkflow.api.retry.RetryDefinition; - public void setRetryDefs(List retryDefs) { - this.retryDefs = retryDefs; - } +public class Retries { + private String refValue; + private List retryDefs; + + public Retries() { + } + + public Retries(List retryDefs) { + this.retryDefs = retryDefs; + } + + public Retries(String refValue) { + this.refValue = refValue; + } + + public String getRefValue() { + return refValue; + } + + public void setRefValue(String refValue) { + this.refValue = refValue; + } + + public List getRetryDefs() { + if (retryDefs == null) { + return Collections.emptyList(); + } + return retryDefs; + } + + public void setRetryDefs(List retryDefs) { + this.retryDefs = retryDefs; + } } diff --git a/api/src/main/java/io/serverlessworkflow/api/workflow/Secrets.java b/api/src/main/java/io/serverlessworkflow/api/workflow/Secrets.java index 2dbb6b31..03edc0fd 100644 --- a/api/src/main/java/io/serverlessworkflow/api/workflow/Secrets.java +++ b/api/src/main/java/io/serverlessworkflow/api/workflow/Secrets.java @@ -15,35 +15,40 @@ */ package io.serverlessworkflow.api.workflow; +import java.util.Collections; import java.util.List; public class Secrets { - private String refValue; - private List secretDefs; - - public Secrets() {} - - public Secrets(String refValue) { - this.refValue = refValue; - } - - public Secrets(List secretDefs) { - this.secretDefs = secretDefs; - } - - public String getRefValue() { - return refValue; - } - - public void setRefValue(String refValue) { - this.refValue = refValue; - } - - public List getSecretDefs() { - return secretDefs; - } - - public void setSecretDefs(List secretDefs) { - this.secretDefs = secretDefs; - } + private String refValue; + private List secretDefs; + + public Secrets() { + } + + public Secrets(String refValue) { + this.refValue = refValue; + } + + public Secrets(List secretDefs) { + this.secretDefs = secretDefs; + } + + public String getRefValue() { + return refValue; + } + + public void setRefValue(String refValue) { + this.refValue = refValue; + } + + public List getSecretDefs() { + if (secretDefs == null) { + return Collections.emptyList(); + } + return secretDefs; + } + + public void setSecretDefs(List secretDefs) { + this.secretDefs = secretDefs; + } } diff --git a/api/src/main/resources/schema/events/eventdef.json b/api/src/main/resources/schema/events/eventdef.json index 6670b856..a585f782 100644 --- a/api/src/main/resources/schema/events/eventdef.json +++ b/api/src/main/resources/schema/events/eventdef.json @@ -24,6 +24,11 @@ "$ref": "../correlation/correlationdef.json" } }, + "dataOnly": { + "type": "boolean", + "default": true, + "description": "If `true`, only the Event payload is accessible to consuming Workflow states. If `false`, both event payload and context attributes should be accessible " + }, "kind": { "type" : "string", "enum": ["consumed", "produced"], diff --git a/api/src/main/resources/schema/events/eventref.json b/api/src/main/resources/schema/events/eventref.json index 76334993..c0e04a7a 100644 --- a/api/src/main/resources/schema/events/eventref.json +++ b/api/src/main/resources/schema/events/eventref.json @@ -16,7 +16,8 @@ "description": "Maximum amount of time (ISO 8601 format) to wait for the result event. If not defined it should default to the actionExecutionTimeout" }, "data": { - "type": "string", + "type": "object", + "existingJavaType": "com.fasterxml.jackson.databind.JsonNode", "description": "Expression which selects parts of the states data output to become the data of the produced event." }, "contextAttributes": { diff --git a/api/src/main/resources/schema/produce/produceevent.json b/api/src/main/resources/schema/produce/produceevent.json index b5bb7d5f..fd3ecdb4 100644 --- a/api/src/main/resources/schema/produce/produceevent.json +++ b/api/src/main/resources/schema/produce/produceevent.json @@ -8,8 +8,14 @@ "minLength": 1 }, "data": { - "type": "string", - "description": "Workflow expression which selects parts of the states data output to become the data of the produced event" + "type": "object", + "description": "Workflow expression which selects parts of the states data output to become the data of the produced event", + "existingJavaType": "com.fasterxml.jackson.databind.JsonNode" + }, + "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 19164ca5..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", @@ -14,6 +14,10 @@ "description": "Workflow unique identifier", "minLength": 1 }, + "key": { + "type": "string", + "description": "Workflow Domain-specific identifier" + }, "name": { "type": "string", "description": "Workflow name", @@ -27,6 +31,14 @@ "type": "string", "description": "Workflow version" }, + "annotations": { + "type": "array", + "description": "List of helpful terms describing the workflows intended purpose, subject areas, or other important qualities", + "minItems": 1, + "items": { + "type": "string" + } + }, "dataInputSchema": { "$ref": "datainputschema/datainputschema.json", "description": "Workflow data input schema" @@ -92,7 +104,9 @@ "$ref": "timeouts/timeoutsdef.json" }, "auth": { - "$ref": "auth/auth.json" + "type": "object", + "existingJavaType": "io.serverlessworkflow.api.workflow.Auth", + "description": "Workflow Auth definitions" }, "states": { "type": "array", @@ -145,10 +159,15 @@ } } }, - "required": [ - "id", - "name", - "version", - "states" - ] -} \ No newline at end of file + "required": [ + "id", + "name", + "version", + "states" + ], + "dependencies": + { + "id": { "not": { "required": ["key"] } }, + "key": { "not": { "required": ["id"] } } + } +} 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 2c6da083..f5b30d07 100644 --- a/api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java +++ b/api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java @@ -25,6 +25,7 @@ import io.serverlessworkflow.api.datainputschema.DataInputSchema; import io.serverlessworkflow.api.defaultdef.DefaultConditionDefinition; 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.FunctionRef; @@ -39,9 +40,11 @@ import io.serverlessworkflow.api.test.utils.WorkflowTestUtils; import io.serverlessworkflow.api.timeouts.WorkflowExecTimeout; import io.serverlessworkflow.api.workflow.Constants; +import io.serverlessworkflow.api.workflow.Events; 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; @@ -103,10 +106,11 @@ public void testSpecExamplesParsing(String workflowLocation) { @ParameterizedTest @ValueSource(strings = {"/features/applicantrequest.json", "/features/applicantrequest.yml"}) - public void testSpecFreatureFunctionRef(String workflowLocation) { + public void testSpecFeatureFunctionRef(String workflowLocation) { Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); assertNotNull(workflow); + assertNull(workflow.getKey()); assertNotNull(workflow.getId()); assertNotNull(workflow.getName()); assertNotNull(workflow.getStates()); @@ -116,6 +120,46 @@ public void testSpecFreatureFunctionRef(String workflowLocation) { assertEquals(1, workflow.getFunctions().getFunctionDefs().size()); } + @ParameterizedTest + @ValueSource( + strings = { + "/features/applicantrequest-with-key.json", + "/features/applicantrequest-with-key.yml" + }) + public void testSpecFeatureFunctionRefWithKey(String workflowLocation) { + Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); + + assertNotNull(workflow); + assertEquals("applicant-key-request", workflow.getKey()); + assertNull(workflow.getId()); + assertNotNull(workflow.getName()); + assertNotNull(workflow.getStates()); + assertTrue(workflow.getStates().size() > 0); + + assertNotNull(workflow.getFunctions()); + assertEquals(1, workflow.getFunctions().getFunctionDefs().size()); + } + + @ParameterizedTest + @ValueSource( + strings = { + "/features/applicantrequest-with-id-and-key.json", + "/features/applicantrequest-with-id-and-key.yml" + }) + public void testSpecFeatureFunctionRefWithIdAndKey(String workflowLocation) { + Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); + + assertNotNull(workflow); + assertEquals("applicant-key-request", workflow.getKey()); + assertEquals("applicant-with-key-and-id", workflow.getId()); + assertNotNull(workflow.getName()); + assertNotNull(workflow.getStates()); + assertTrue(workflow.getStates().size() > 0); + + assertNotNull(workflow.getFunctions()); + assertEquals(1, workflow.getFunctions().getFunctionDefs().size()); + } + @ParameterizedTest @ValueSource(strings = {"/features/vetappointment.json", "/features/vetappointment.yml"}) public void testSpecFreatureEventRef(String workflowLocation) { @@ -221,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()); @@ -625,7 +675,7 @@ public void testAuthBasic(String workflowLocation) { assertNotNull(workflow.getName()); assertNotNull(workflow.getAuth()); - AuthDefinition auth = workflow.getAuth(); + AuthDefinition auth = workflow.getAuth().getAuthDefs().get(0); assertNotNull(auth.getName()); assertEquals("authname", auth.getName()); assertNotNull(auth.getScheme()); @@ -645,7 +695,7 @@ public void testAuthBearer(String workflowLocation) { assertNotNull(workflow.getName()); assertNotNull(workflow.getAuth()); - AuthDefinition auth = workflow.getAuth(); + AuthDefinition auth = workflow.getAuth().getAuthDefs().get(0); assertNotNull(auth.getName()); assertEquals("authname", auth.getName()); assertNotNull(auth.getScheme()); @@ -664,7 +714,7 @@ public void testAuthOAuth(String workflowLocation) { assertNotNull(workflow.getName()); assertNotNull(workflow.getAuth()); - AuthDefinition auth = workflow.getAuth(); + AuthDefinition auth = workflow.getAuth().getAuthDefs().get(0); assertNotNull(auth.getName()); assertEquals("authname", auth.getName()); assertNotNull(auth.getScheme()); @@ -838,4 +888,39 @@ public void testFunctionInvoke(String workflowLocation) { assertNotNull(action3.getEventRef().getInvoke()); assertEquals(EventRef.Invoke.ASYNC, action3.getEventRef().getInvoke()); } + + @ParameterizedTest + @ValueSource(strings = {"/features/annotations.json", "/features/annotations.yml"}) + public void testAnnotations(String workflowLocation) { + Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); + + assertNotNull(workflow); + assertNotNull(workflow.getId()); + assertNotNull(workflow.getName()); + + assertNotNull(workflow.getAnnotations()); + List annotations = workflow.getAnnotations(); + assertEquals(4, annotations.size()); + } + + @ParameterizedTest + @ValueSource(strings = {"/features/eventdefdataonly.json", "/features/eventdefdataonly.yml"}) + public void testEventDefDataOnly(String workflowLocation) { + Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); + + assertNotNull(workflow); + assertNotNull(workflow.getId()); + assertNotNull(workflow.getName()); + + assertNotNull(workflow.getEvents()); + Events events = workflow.getEvents(); + assertNotNull(workflow.getEvents().getEventDefs()); + assertEquals(2, events.getEventDefs().size()); + EventDefinition eventDefOne = events.getEventDefs().get(0); + EventDefinition eventDefTwo = events.getEventDefs().get(1); + assertEquals("visaApprovedEvent", eventDefOne.getName()); + assertFalse(eventDefOne.isDataOnly()); + assertEquals("visaRejectedEvent", eventDefTwo.getName()); + assertTrue(eventDefTwo.isDataOnly()); + } } diff --git a/api/src/test/java/io/serverlessworkflow/api/test/WorkflowToMarkupTest.java b/api/src/test/java/io/serverlessworkflow/api/test/WorkflowToMarkupTest.java index 514375e1..ff43f167 100644 --- a/api/src/test/java/io/serverlessworkflow/api/test/WorkflowToMarkupTest.java +++ b/api/src/test/java/io/serverlessworkflow/api/test/WorkflowToMarkupTest.java @@ -16,7 +16,9 @@ package io.serverlessworkflow.api.test; import static io.serverlessworkflow.api.states.DefaultState.Type.SLEEP; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import io.serverlessworkflow.api.Workflow; import io.serverlessworkflow.api.auth.AuthDefinition; @@ -29,6 +31,7 @@ import io.serverlessworkflow.api.schedule.Schedule; import io.serverlessworkflow.api.start.Start; import io.serverlessworkflow.api.states.SleepState; +import io.serverlessworkflow.api.workflow.Auth; import io.serverlessworkflow.api.workflow.Events; import io.serverlessworkflow.api.workflow.Functions; import java.util.Arrays; @@ -162,22 +165,24 @@ public void testAuth() { .withVersion("1.0") .withStart(new Start()) .withAuth( - new AuthDefinition() - .withName("authname") - .withScheme(AuthDefinition.Scheme.BASIC) - .withBasicauth( - new BasicAuthDefinition() - .withUsername("testuser") - .withPassword("testPassword"))); + new Auth( + new AuthDefinition() + .withName("authname") + .withScheme(AuthDefinition.Scheme.BASIC) + .withBasicauth( + new BasicAuthDefinition() + .withUsername("testuser") + .withPassword("testPassword")))); assertNotNull(workflow); assertNotNull(workflow.getAuth()); - assertNotNull(workflow.getAuth().getName()); - assertEquals("authname", workflow.getAuth().getName()); - assertNotNull(workflow.getAuth().getScheme()); - assertEquals("basic", workflow.getAuth().getScheme().value()); - assertNotNull(workflow.getAuth().getBasicauth()); - assertEquals("testuser", workflow.getAuth().getBasicauth().getUsername()); - assertEquals("testPassword", workflow.getAuth().getBasicauth().getPassword()); + assertNotNull(workflow.getAuth().getAuthDefs().get(0)); + assertEquals("authname", workflow.getAuth().getAuthDefs().get(0).getName()); + assertNotNull(workflow.getAuth().getAuthDefs().get(0).getScheme()); + assertEquals("basic", workflow.getAuth().getAuthDefs().get(0).getScheme().value()); + assertNotNull(workflow.getAuth().getAuthDefs().get(0).getBasicauth()); + assertEquals("testuser", workflow.getAuth().getAuthDefs().get(0).getBasicauth().getUsername()); + assertEquals( + "testPassword", workflow.getAuth().getAuthDefs().get(0).getBasicauth().getPassword()); } } diff --git a/api/src/test/resources/examples/applicantrequest.json b/api/src/test/resources/examples/applicantrequest.json index 1621c2bd..652e361b 100644 --- a/api/src/test/resources/examples/applicantrequest.json +++ b/api/src/test/resources/examples/applicantrequest.json @@ -1,5 +1,6 @@ { "id": "applicantrequest", + "key": "applicant-key-request", "version": "1.0", "specVersion": "0.8", "name": "Applicant Request Decision Workflow", diff --git a/api/src/test/resources/features/annotations.json b/api/src/test/resources/features/annotations.json new file mode 100644 index 00000000..90ed6dd1 --- /dev/null +++ b/api/src/test/resources/features/annotations.json @@ -0,0 +1,6 @@ +{ + "id" : "test-workflow", + "name" : "test-workflow-name", + "version" : "1.0", + "annotations": ["a", "b", "c", "d"] +} \ No newline at end of file diff --git a/api/src/test/resources/features/annotations.yml b/api/src/test/resources/features/annotations.yml new file mode 100644 index 00000000..54e359ae --- /dev/null +++ b/api/src/test/resources/features/annotations.yml @@ -0,0 +1,8 @@ +id: test-workflow +name: test-workflow-name +version: '1.0' +annotations: + - a + - b + - c + - d diff --git a/api/src/test/resources/features/applicantrequest-with-id-and-key.json b/api/src/test/resources/features/applicantrequest-with-id-and-key.json new file mode 100644 index 00000000..405d7c36 --- /dev/null +++ b/api/src/test/resources/features/applicantrequest-with-id-and-key.json @@ -0,0 +1,60 @@ +{ + "id": "applicant-with-key-and-id", + "key": "applicant-key-request", + "version": "1.0", + "specVersion": "0.8", + "name": "Applicant Request Decision Workflow", + "description": "Determine if applicant request is valid", + "start": "CheckApplication", + "functions": [ + { + "name": "sendRejectionEmailFunction", + "operation": "http://myapis.org/applicationapi.json#emailRejection" + } + ], + "states":[ + { + "name":"CheckApplication", + "type":"switch", + "dataConditions": [ + { + "condition": "${ .applicants | .age >= 18 }", + "transition": "StartApplication" + }, + { + "condition": "${ .applicants | .age < 18 }", + "transition": "RejectApplication" + } + ], + "defaultCondition": { + "transition": "RejectApplication" + } + }, + { + "name": "StartApplication", + "type": "operation", + "actions": [ + { + "subFlowRef": "startApplicationWorkflowId" + } + ], + "end": true + }, + { + "name":"RejectApplication", + "type":"operation", + "actionMode":"sequential", + "actions":[ + { + "functionRef": { + "refName": "sendRejectionEmailFunction", + "arguments": { + "applicant": "${ .applicant }" + } + } + } + ], + "end": true + } + ] +} \ No newline at end of file diff --git a/api/src/test/resources/features/applicantrequest-with-id-and-key.yml b/api/src/test/resources/features/applicantrequest-with-id-and-key.yml new file mode 100644 index 00000000..8a123663 --- /dev/null +++ b/api/src/test/resources/features/applicantrequest-with-id-and-key.yml @@ -0,0 +1,34 @@ +id: applicant-with-key-and-id +key: applicant-key-request +version: '1.0' +specVersion: '0.8' +name: Applicant Request Decision Workflow +description: Determine if applicant request is valid +start: CheckApplication +functions: + - name: sendRejectionEmailFunction + operation: http://myapis.org/applicationapi.json#emailRejection +states: + - name: CheckApplication + type: switch + dataConditions: + - condition: "${ .applicants | .age >= 18 }" + transition: StartApplication + - condition: "${ .applicants | .age < 18 }" + transition: RejectApplication + defaultCondition: + transition: RejectApplication + - name: StartApplication + type: operation + actions: + - subFlowRef: startApplicationWorkflowId + end: true + - name: RejectApplication + type: operation + actionMode: sequential + actions: + - functionRef: + refName: sendRejectionEmailFunction + arguments: + applicant: "${ .applicant }" + end: true diff --git a/api/src/test/resources/features/applicantrequest-with-key.json b/api/src/test/resources/features/applicantrequest-with-key.json new file mode 100644 index 00000000..f0481b00 --- /dev/null +++ b/api/src/test/resources/features/applicantrequest-with-key.json @@ -0,0 +1,59 @@ +{ + "key": "applicant-key-request", + "version": "1.0", + "specVersion": "0.8", + "name": "Applicant Request Decision Workflow", + "description": "Determine if applicant request is valid", + "start": "CheckApplication", + "functions": [ + { + "name": "sendRejectionEmailFunction", + "operation": "http://myapis.org/applicationapi.json#emailRejection" + } + ], + "states":[ + { + "name":"CheckApplication", + "type":"switch", + "dataConditions": [ + { + "condition": "${ .applicants | .age >= 18 }", + "transition": "StartApplication" + }, + { + "condition": "${ .applicants | .age < 18 }", + "transition": "RejectApplication" + } + ], + "defaultCondition": { + "transition": "RejectApplication" + } + }, + { + "name": "StartApplication", + "type": "operation", + "actions": [ + { + "subFlowRef": "startApplicationWorkflowId" + } + ], + "end": true + }, + { + "name":"RejectApplication", + "type":"operation", + "actionMode":"sequential", + "actions":[ + { + "functionRef": { + "refName": "sendRejectionEmailFunction", + "arguments": { + "applicant": "${ .applicant }" + } + } + } + ], + "end": true + } + ] +} \ No newline at end of file diff --git a/api/src/test/resources/features/applicantrequest-with-key.yml b/api/src/test/resources/features/applicantrequest-with-key.yml new file mode 100644 index 00000000..85beed74 --- /dev/null +++ b/api/src/test/resources/features/applicantrequest-with-key.yml @@ -0,0 +1,33 @@ +key: applicant-key-request +version: '1.0' +specVersion: '0.8' +name: Applicant Request Decision Workflow +description: Determine if applicant request is valid +start: CheckApplication +functions: + - name: sendRejectionEmailFunction + operation: http://myapis.org/applicationapi.json#emailRejection +states: + - name: CheckApplication + type: switch + dataConditions: + - condition: "${ .applicants | .age >= 18 }" + transition: StartApplication + - condition: "${ .applicants | .age < 18 }" + transition: RejectApplication + defaultCondition: + transition: RejectApplication + - name: StartApplication + type: operation + actions: + - subFlowRef: startApplicationWorkflowId + end: true + - name: RejectApplication + type: operation + actionMode: sequential + actions: + - functionRef: + refName: sendRejectionEmailFunction + arguments: + applicant: "${ .applicant }" + end: true diff --git a/api/src/test/resources/features/authbasic.json b/api/src/test/resources/features/authbasic.json index 92397db4..31e86599 100644 --- a/api/src/test/resources/features/authbasic.json +++ b/api/src/test/resources/features/authbasic.json @@ -1,13 +1,15 @@ { - "id" : "test-workflow", - "name" : "test-workflow-name", - "version" : "1.0", - "auth" : { - "name" : "authname", - "scheme" : "basic", - "properties" : { - "username" : "testuser", - "password" : "testpassword" + "id": "test-workflow", + "name": "test-workflow-name", + "version": "1.0", + "auth": [ + { + "name": "authname", + "scheme": "basic", + "properties": { + "username": "testuser", + "password": "testpassword" + } } - } + ] } \ No newline at end of file diff --git a/api/src/test/resources/features/authbasic.yml b/api/src/test/resources/features/authbasic.yml index 963dc63e..e04d1f7e 100644 --- a/api/src/test/resources/features/authbasic.yml +++ b/api/src/test/resources/features/authbasic.yml @@ -2,8 +2,8 @@ id: test-workflow name: test-workflow-name version: '1.0' auth: - name: authname - scheme: basic - properties: - username: testuser - password: testpassword + - name: authname + scheme: basic + properties: + username: testuser + password: testpassword diff --git a/api/src/test/resources/features/authbearer.json b/api/src/test/resources/features/authbearer.json index 304d1685..be7c037a 100644 --- a/api/src/test/resources/features/authbearer.json +++ b/api/src/test/resources/features/authbearer.json @@ -1,12 +1,14 @@ { - "id" : "test-workflow", - "name" : "test-workflow-name", - "version" : "1.0", - "auth" : { - "name" : "authname", - "scheme" : "bearer", - "properties" : { - "token" : "testtoken" + "id": "test-workflow", + "name": "test-workflow-name", + "version": "1.0", + "auth": [ + { + "name": "authname", + "scheme": "bearer", + "properties": { + "token": "testtoken" + } } - } + ] } \ No newline at end of file diff --git a/api/src/test/resources/features/authbearer.yml b/api/src/test/resources/features/authbearer.yml index 0a815386..292fa3c2 100644 --- a/api/src/test/resources/features/authbearer.yml +++ b/api/src/test/resources/features/authbearer.yml @@ -2,7 +2,7 @@ id: test-workflow name: test-workflow-name version: '1.0' auth: - name: authname - scheme: bearer - properties: - token: testtoken + - name: authname + scheme: bearer + properties: + token: testtoken diff --git a/api/src/test/resources/features/authoauth.json b/api/src/test/resources/features/authoauth.json index da845606..10b76d70 100644 --- a/api/src/test/resources/features/authoauth.json +++ b/api/src/test/resources/features/authoauth.json @@ -2,7 +2,7 @@ "id" : "test-workflow", "name" : "test-workflow-name", "version" : "1.0", - "auth" : { + "auth" : [{ "name" : "authname", "scheme" : "oauth2", "properties" : { @@ -11,5 +11,5 @@ "clientId": "${ $SECRETS.clientid }", "clientSecret": "${ $SECRETS.clientsecret }" } - } + }] } \ No newline at end of file diff --git a/api/src/test/resources/features/authoauth.yml b/api/src/test/resources/features/authoauth.yml index 8741297c..cb2c52ba 100644 --- a/api/src/test/resources/features/authoauth.yml +++ b/api/src/test/resources/features/authoauth.yml @@ -2,10 +2,10 @@ id: test-workflow name: test-workflow-name version: '1.0' auth: - name: authname - scheme: oauth2 - properties: - authority: testauthority - grantType: clientCredentials - clientId: "${ $SECRETS.clientid }" - clientSecret: "${ $SECRETS.clientsecret }" + - name: authname + scheme: oauth2 + properties: + authority: testauthority + grantType: clientCredentials + clientId: "${ $SECRETS.clientid }" + clientSecret: "${ $SECRETS.clientsecret }" diff --git a/api/src/test/resources/features/eventdefdataonly.json b/api/src/test/resources/features/eventdefdataonly.json new file mode 100644 index 00000000..a181b864 --- /dev/null +++ b/api/src/test/resources/features/eventdefdataonly.json @@ -0,0 +1,73 @@ +{ + "id": "eventdefdataonly", + "version": "1.0", + "specVersion": "0.8", + "name": "Event Definition Data Only Test", + "description": "Event Definition Data Only Test", + "start": "CheckVisaStatus", + "events": [ + { + "name": "visaApprovedEvent", + "type": "VisaApproved", + "source": "visaCheckSource", + "dataOnly": false + }, + { + "name": "visaRejectedEvent", + "type": "VisaRejected", + "source": "visaCheckSource" + } + ], + "states":[ + { + "name":"CheckVisaStatus", + "type":"switch", + "eventConditions": [ + { + "eventRef": "visaApprovedEvent", + "transition": "HandleApprovedVisa" + }, + { + "eventRef": "visaRejectedEvent", + "transition": "HandleRejectedVisa" + } + ], + "timeouts": { + "eventTimeout": "PT1H" + }, + "defaultCondition": { + "transition": "HandleNoVisaDecision" + } + }, + { + "name": "HandleApprovedVisa", + "type": "operation", + "actions": [ + { + "subFlowRef": "handleApprovedVisaWorkflowID" + } + ], + "end": true + }, + { + "name": "HandleRejectedVisa", + "type": "operation", + "actions": [ + { + "subFlowRef": "handleRejectedVisaWorkflowID" + } + ], + "end": true + }, + { + "name": "HandleNoVisaDecision", + "type": "operation", + "actions": [ + { + "subFlowRef": "handleNoVisaDecisionWorkflowId" + } + ], + "end": true + } + ] +} \ No newline at end of file diff --git a/api/src/test/resources/features/eventdefdataonly.yml b/api/src/test/resources/features/eventdefdataonly.yml new file mode 100644 index 00000000..e67a9ede --- /dev/null +++ b/api/src/test/resources/features/eventdefdataonly.yml @@ -0,0 +1,41 @@ +id: eventdefdataonly +version: '1.0' +specVersion: '0.8' +name: Event Definition Data Only Test +description: Event Definition Data Only Test +start: CheckVisaStatus +events: + - name: visaApprovedEvent + type: VisaApproved + source: visaCheckSource + dataOnly: false + - name: visaRejectedEvent + type: VisaRejected + source: visaCheckSource +states: + - name: CheckVisaStatus + type: switch + eventConditions: + - eventRef: visaApprovedEvent + transition: HandleApprovedVisa + - eventRef: visaRejectedEvent + transition: HandleRejectedVisa + timeouts: + eventTimeout: PT1H + defaultCondition: + transition: HandleNoVisaDecision + - name: HandleApprovedVisa + type: operation + actions: + - subFlowRef: handleApprovedVisaWorkflowID + end: true + - name: HandleRejectedVisa + type: operation + actions: + - subFlowRef: handleRejectedVisaWorkflowID + end: true + - name: HandleNoVisaDecision + type: operation + actions: + - subFlowRef: handleNoVisaDecisionWorkflowId + end: true 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/diagram/pom.xml b/diagram/pom.xml index 41f8337a..d68a56cb 100644 --- a/diagram/pom.xml +++ b/diagram/pom.xml @@ -1,17 +1,14 @@ - + 4.0.0 io.serverlessworkflow serverlessworkflow-parent - 4.0.0-SNAPSHOT + 4.3.0-SNAPSHOT serverlessworkflow-diagram Serverless Workflow :: Diagram - ${project.parent.version} jar Java SDK for Serverless Workflow Specification @@ -97,13 +94,13 @@ - - + + - - + + @@ -130,26 +127,6 @@ - - com.coveo - fmt-maven-plugin - - src/main/java - src/test/java - false - .*\.java - false - false - - - - - - format - - - - \ No newline at end of file diff --git a/diagram/src/main/java/io/serverlessworkflow/diagram/WorkflowDiagramImpl.java b/diagram/src/main/java/io/serverlessworkflow/diagram/WorkflowDiagramImpl.java index 5a2d4028..1fb5e656 100644 --- a/diagram/src/main/java/io/serverlessworkflow/diagram/WorkflowDiagramImpl.java +++ b/diagram/src/main/java/io/serverlessworkflow/diagram/WorkflowDiagramImpl.java @@ -26,9 +26,14 @@ public class WorkflowDiagramImpl implements WorkflowDiagram { + public static final String DEFAULT_TEMPLATE = "workflow-template"; + @SuppressWarnings("unused") private String source; + @SuppressWarnings("unused") + private String template = DEFAULT_TEMPLATE; + private Workflow workflow; private boolean showLegend = false; @@ -46,12 +51,18 @@ public WorkflowDiagram setSource(String source) { return this; } + @Override + public WorkflowDiagram setTemplate(String template) { + this.template = template; + return this; + } + @Override public String getSvgDiagram() throws Exception { if (workflow == null) { throw new IllegalAccessException("Unable to get diagram - no workflow set."); } - String diagramSource = WorkflowToPlantuml.convert(workflow, showLegend); + String diagramSource = WorkflowToPlantuml.convert(template, workflow, showLegend); SourceStringReader reader = new SourceStringReader(diagramSource); final ByteArrayOutputStream os = new ByteArrayOutputStream(); reader.generateImage(os, new FileFormatOption(FileFormat.SVG)); diff --git a/diagram/src/main/java/io/serverlessworkflow/diagram/utils/WorkflowToPlantuml.java b/diagram/src/main/java/io/serverlessworkflow/diagram/utils/WorkflowToPlantuml.java index acc112b8..956bcbeb 100644 --- a/diagram/src/main/java/io/serverlessworkflow/diagram/utils/WorkflowToPlantuml.java +++ b/diagram/src/main/java/io/serverlessworkflow/diagram/utils/WorkflowToPlantuml.java @@ -22,11 +22,12 @@ import org.thymeleaf.context.Context; public class WorkflowToPlantuml { - public static String convert(Workflow workflow, boolean showLegend) { + + public static String convert(String template, Workflow workflow, boolean showLegend) { TemplateEngine plantUmlTemplateEngine = ThymeleafConfig.templateEngine; Context context = new Context(); context.setVariable("diagram", new WorkflowDiagramModel(workflow, showLegend)); - return plantUmlTemplateEngine.process("workflow-template", context); + return plantUmlTemplateEngine.process(template, context); } } diff --git a/diagram/src/test/java/io/serverlessworkflow/diagram/test/CustomTemplateWorkflowDiagramTest.java b/diagram/src/test/java/io/serverlessworkflow/diagram/test/CustomTemplateWorkflowDiagramTest.java new file mode 100644 index 00000000..5c9b7038 --- /dev/null +++ b/diagram/src/test/java/io/serverlessworkflow/diagram/test/CustomTemplateWorkflowDiagramTest.java @@ -0,0 +1,53 @@ +/* + * 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.diagram.test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import io.serverlessworkflow.api.Workflow; +import io.serverlessworkflow.api.interfaces.WorkflowDiagram; +import io.serverlessworkflow.diagram.WorkflowDiagramImpl; +import io.serverlessworkflow.diagram.test.utils.DiagramTestUtils; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class CustomTemplateWorkflowDiagramTest { + + @ParameterizedTest + @ValueSource(strings = {"/examples/applicantrequest.json", "/examples/applicantrequest.yml"}) + public void testSpecExamplesParsing(String workflowLocation) throws Exception { + + Workflow workflow = Workflow.fromSource(DiagramTestUtils.readWorkflowFile(workflowLocation)); + + assertNotNull(workflow); + assertNotNull(workflow.getId()); + assertNotNull(workflow.getName()); + assertNotNull(workflow.getStates()); + + WorkflowDiagram workflowDiagram = + new WorkflowDiagramImpl() + .showLegend(true) + .setWorkflow(workflow) + .setTemplate("custom-template"); + + String diagramSVG = workflowDiagram.getSvgDiagram(); + + Assertions.assertNotNull(diagramSVG); + // custom template uses customcolor in the legend + Assertions.assertTrue(diagramSVG.contains("customcolor")); + } +} diff --git a/diagram/src/test/resources/templates/plantuml/custom-template.txt b/diagram/src/test/resources/templates/plantuml/custom-template.txt new file mode 100644 index 00000000..e162afc5 --- /dev/null +++ b/diagram/src/test/resources/templates/plantuml/custom-template.txt @@ -0,0 +1,46 @@ +@startuml +skinparam backgroundColor White +skinparam legendBackgroundColor White +skinparam legendBorderColor White +skinparam state { + StartColor Green + EndColor Orange + BackgroundColor GhostWhite + BackgroundColor<< workflow >> White + BorderColor Black + ArrowColor Black + + BorderColor<< event >> #7fe5f0 + BorderColor<< operation >> #bada55 + BorderColor<< switch >> #92a0f2 + BorderColor<< sleep >> #b83b5e + BorderColor<< parallel >> #6a2c70 + BorderColor<< inject >> #1e5f74 + BorderColor<< foreach >> #931a25 + BorderColor<< callback >> #ffcb8e +} +state "[(${diagram.title})]" as workflow << workflow >> { + +[# th:each="stateDef : ${diagram.modelStateDefs}" ] +[(${stateDef.toString()})] +[/] + +[# th:each="state : ${diagram.modelStates}" ] +[(${state.toString()})] +[/] + +[# th:each="connection : ${diagram.modelConnections}" ] +[(${connection.toString()})] +[/] + +} + +[# th:if="${diagram.showLegend}" ] +legend center +State Types and Border Colors: +| Event | Operation | Switch | Sleep | Parallel | Inject | ForEach | CallBack | +|<#7fe5f0>|<#bada55>|<#92a0f2>|<#b83b5e>|<#6a2c70>|<#1e5f74>|<#931a25>|| +endlegend +[/] + +@enduml \ No newline at end of file diff --git a/pom.xml b/pom.xml index 5ef1c788..abe203f0 100644 --- a/pom.xml +++ b/pom.xml @@ -1,74 +1,96 @@ - - 4.0.0 + + 4.0.0 - io.serverlessworkflow - serverlessworkflow-parent - 4.0.0-SNAPSHOT - pom + io.serverlessworkflow + serverlessworkflow-parent + 4.3.0-SNAPSHOT + 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 + - - api - spi - validation - diagram - utils - + + api + spi + validation + diagram + utils + - - 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 + + 17 + ${java.version} + ${java.version} + ${java.version} + UTF-8 + 3.9.7 - 1.7.25 - 2.10.3 - 2.0.1.Final - 6.0 - 5.${version.org.junit.minor} - ${version.org.junit} - 3.0.0 - 1.1.3 - 3.13.2 - 1.0.1 - 3.9 - 1.3 - 1.5.0 - 1.12.1 - 20200518 - 3.0.11.RELEASE - 8059 - 0.17.0 - 2.9 + + 3.2.1 + 3.6.0 + 3.14.0 + 3.1.4 + 3.6.1 + 3.5.3 + 2.25 + 3.2.8 + 3.4.2 + ${java.version} + 1.2.2 + 3.11.2 + 3.1.1 + 3.3.1 + 3.5.3 + 1.7.0 - - true - + 1.5.18 + 2.19.2 + 1.5.8 + 3.18.0 + 0.18.1 + 3.0 + 3.1.1 + 1.5.3 + 3.27.3 + 5.13.4 + 5.18.0 + 2.0.17 + 8059 + 3.1.3.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} + + + jakarta.validation + jakarta.validation-api + ${version.jakarta.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-nop + ${version.org.slf4j} + test + + + + + + + + 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.sonatype.plugins + nexus-staging-maven-plugin + ${version.nexus.plugin} + true + + ossrh + https://oss.sonatype.org/ + true + + + + 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.spotify.fmt + fmt-maven-plugin + + src/main/java + src/test/java + false + .*\.java + false + false + + + + + + format + + + + + + 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} + + false + + + + + - - - - 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.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 - - - + + + ossrh-snapshots + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + - - - - - 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:MaxPermSize=256m - - - - org.apache.maven.plugins - maven-failsafe-plugin - ${version.failsafe.plugin} - - -Xmx1024m -XX:MaxPermSize=256m - - - - org.apache.maven.plugins - maven-checkstyle-plugin - ${version.checkstyle.plugin} - - - com.coveo - fmt-maven-plugin - ${version.fmt-maven-plugin} - - - - + + + central + Central Repository + https://repo.maven.apache.org/maven2 + default + + false + + + - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - + + + release + + + + org.apache.maven.plugins + maven-gpg-plugin + + + --pinentry-mode + loopback + + + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + package + + jar + + + + + + + + - - - central - Central Repository - https://repo.maven.apache.org/maven2 - default - - false - - - - + \ No newline at end of file diff --git a/spi/pom.xml b/spi/pom.xml index e713102e..1bb2c028 100644 --- a/spi/pom.xml +++ b/spi/pom.xml @@ -1,17 +1,14 @@ - + 4.0.0 io.serverlessworkflow serverlessworkflow-parent - 4.0.0-SNAPSHOT + 4.3.0-SNAPSHOT serverlessworkflow-spi Serverless Workflow :: SPI - ${project.parent.version} jar Java SDK for Serverless Workflow Specification @@ -69,13 +66,13 @@ - - + + - - + + @@ -102,26 +99,6 @@ - - com.coveo - fmt-maven-plugin - - src/main/java - src/test/java - false - .*\.java - false - false - - - - - - format - - - - \ No newline at end of file diff --git a/utils/pom.xml b/utils/pom.xml index be6a7444..23a23711 100644 --- a/utils/pom.xml +++ b/utils/pom.xml @@ -1,17 +1,14 @@ - + 4.0.0 io.serverlessworkflow serverlessworkflow-parent - 4.0.0-SNAPSHOT + 4.3.0-SNAPSHOT serverlessworkflow-util Serverless Workflow :: Utils - ${project.parent.version} jar Java SDK for Serverless Workflow Specification @@ -69,13 +66,13 @@ - - + + - - + + @@ -102,26 +99,6 @@ - - com.coveo - fmt-maven-plugin - - src/main/java - src/test/java - false - .*\.java - false - false - - - - - - format - - - - \ No newline at end of file diff --git a/utils/src/main/java/io/serverlessworkflow/utils/WorkflowUtils.java b/utils/src/main/java/io/serverlessworkflow/utils/WorkflowUtils.java index 29b8c7cc..a5673b40 100644 --- a/utils/src/main/java/io/serverlessworkflow/utils/WorkflowUtils.java +++ b/utils/src/main/java/io/serverlessworkflow/utils/WorkflowUtils.java @@ -574,7 +574,7 @@ public static JsonNode mergeNodes(JsonNode mainNode, JsonNode updateNode) { if (mainNode instanceof ObjectNode) { // Overwrite field JsonNode value = updateNode.get(fieldName); - ((ObjectNode) mainNode).put(fieldName, value); + ((ObjectNode) mainNode).set(fieldName, value); } } } @@ -591,7 +591,7 @@ public static JsonNode mergeNodes(JsonNode mainNode, JsonNode updateNode) { * @return original, main node with field added */ public static JsonNode addNode(JsonNode mainNode, JsonNode toAddNode, String fieldName) { - ((ObjectNode) mainNode).put(fieldName, toAddNode); + ((ObjectNode) mainNode).set(fieldName, toAddNode); return mainNode; } @@ -604,7 +604,7 @@ public static JsonNode addNode(JsonNode mainNode, JsonNode toAddNode, String fie * @return original, main node with array added */ public static JsonNode addArray(JsonNode mainNode, ArrayNode toAddArray, String arrayName) { - ((ObjectNode) mainNode).put(arrayName, toAddArray); + ((ObjectNode) mainNode).set(arrayName, toAddArray); return mainNode; } @@ -618,7 +618,7 @@ public static JsonNode addArray(JsonNode mainNode, ArrayNode toAddArray, String */ public static JsonNode addFieldValue(JsonNode mainNode, Object toAddValue, String fieldName) { ObjectMapper mapper = new ObjectMapper(); - ((ObjectNode) mainNode).put(fieldName, mapper.valueToTree(toAddValue)); + ((ObjectNode) mainNode).set(fieldName, mapper.valueToTree(toAddValue)); return mainNode; } } diff --git a/validation/pom.xml b/validation/pom.xml index 7219d952..63c7901f 100644 --- a/validation/pom.xml +++ b/validation/pom.xml @@ -1,17 +1,14 @@ - + 4.0.0 io.serverlessworkflow serverlessworkflow-parent - 4.0.0-SNAPSHOT + 4.3.0-SNAPSHOT serverlessworkflow-validation Serverless Workflow :: Validation - ${project.parent.version} jar Java SDK for Serverless Workflow Specification @@ -35,12 +32,10 @@ org.apache.commons commons-lang3 - - com.github.erosb - everit-json-schema + com.networknt + json-schema-validator - org.junit.jupiter @@ -93,13 +88,13 @@ - - + + - - + + @@ -126,26 +121,6 @@ - - com.coveo - fmt-maven-plugin - - src/main/java - src/test/java - false - .*\.java - false - false - - - - - - format - - - - diff --git a/validation/src/main/java/io/serverlessworkflow/validation/WorkflowValidatorImpl.java b/validation/src/main/java/io/serverlessworkflow/validation/WorkflowValidatorImpl.java index 130e4c24..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,42 +80,34 @@ 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); - } - - List functions = - workflow.getFunctions() != null ? workflow.getFunctions().getFunctionDefs() : null; + } else if (workflow == null) { + workflow = Workflow.fromSource(source); + } - List events = - workflow.getEvents() != null ? workflow.getEvents().getEventDefs() : null; + List functions = + workflow.getFunctions() != null ? workflow.getFunctions().getFunctionDefs() : null; - if (workflow.getId() == null || workflow.getId().trim().isEmpty()) { - addValidationError("Workflow id should not be empty", ValidationError.WORKFLOW_VALIDATION); - } + List events = + workflow.getEvents() != null ? workflow.getEvents().getEventDefs() : null; - if (workflow.getName() == null || workflow.getName().trim().isEmpty()) { - addValidationError( - "Workflow name should not be empty", ValidationError.WORKFLOW_VALIDATION); - } - - if (workflow.getStart() == null) { - addValidationError( - "Workflow must define a starting state", 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.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)) { @@ -144,226 +115,209 @@ public List validate() { break; } } - 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 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 (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 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 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 (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 InjectState) { + InjectState injectState = (InjectState) s; + if (injectState.getData() == null || injectState.getData().isEmpty()) { + addValidationError( + "InjectState should have non-null data", + ValidationError.WORKFLOW_VALIDATION); + } + } - if (forEachState.getIterationParam() == null - || forEachState.getIterationParam().isEmpty()) { - addValidationError( - "ForEach state should have a valid iteration parameter", - 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 @@ -392,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); @@ -410,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 8baf9dfa..6ccef44f 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; @@ -44,9 +56,9 @@ public void testIncompleteJsonWithSchemaValidation() { public void testIncompleteYamlWithSchemaValidation() { WorkflowValidator workflowValidator = new WorkflowValidatorImpl(); List validationErrors = - workflowValidator.setSource("---\n" + "id: abc\n").validate(); + workflowValidator.setSource("---\n" + "key: abc\n").validate(); Assertions.assertNotNull(validationErrors); - Assertions.assertEquals(3, validationErrors.size()); + Assertions.assertEquals(4, validationErrors.size()); } @Test @@ -67,13 +79,10 @@ public void testFromIncompleteWorkflow() { WorkflowValidator workflowValidator = new WorkflowValidatorImpl(); List validationErrors = workflowValidator.setWorkflow(workflow).validate(); Assertions.assertNotNull(validationErrors); - Assertions.assertEquals(2, validationErrors.size()); - - Assertions.assertEquals( - "Workflow name should not be empty", validationErrors.get(0).getMessage()); + Assertions.assertEquals(1, validationErrors.size()); Assertions.assertEquals( "No state name found that matches the workflow start definition", - validationErrors.get(1).getMessage()); + validationErrors.get(0).getMessage()); } @Test @@ -96,6 +105,26 @@ public void testWorkflowMissingStates() { Assertions.assertEquals("No states found", validationErrors.get(0).getMessage()); } + @Test + public void testWorkflowMissingStatesIdAndKey() { + WorkflowValidator workflowValidator = new WorkflowValidatorImpl(); + List validationErrors = + workflowValidator + .setSource( + "{\n" + + "\t\"name\": \"test workflow\",\n" + + " \"version\": \"1.0\",\n" + + " \"start\": \"SomeState\",\n" + + " \"states\": []\n" + + "}") + .validate(); + Assertions.assertNotNull(validationErrors); + Assertions.assertEquals(1, validationErrors.size()); + + Assertions.assertEquals( + "$: required property 'id' not found", validationErrors.get(0).getMessage()); + } + @Test public void testOperationStateNoFunctionRef() { WorkflowValidator workflowValidator = new WorkflowValidatorImpl(); @@ -104,7 +133,7 @@ public void testOperationStateNoFunctionRef() { .setSource( "{\n" + "\"id\": \"checkInbox\",\n" - + " \"name\": \"Check Inbox Workflow\",\n" + + "\"name\": \"Check Inbox Workflow\",\n" + "\"description\": \"Periodically Check Inbox\",\n" + "\"version\": \"1.0\",\n" + "\"start\": \"CheckInbox\",\n" @@ -147,4 +176,161 @@ public void testOperationStateNoFunctionRef() { "Operation State action functionRef does not reference an existing workflow function definition", validationErrors.get(0).getMessage()); } + + @Test + public void testValidateWorkflowForOptionalStartStateAndWorkflowName() { + Workflow workflow = + new Workflow() + .withId("test-workflow") + .withVersion("1.0") + .withStates( + Arrays.asList( + new SleepState() + .withName("sleepState") + .withType(SLEEP) + .withEnd(new End()) + .withDuration("PT1M"))); + + WorkflowValidator workflowValidator = new WorkflowValidatorImpl(); + List validationErrors = workflowValidator.setWorkflow(workflow).validate(); + Assertions.assertNotNull(validationErrors); + Assertions.assertEquals(0, validationErrors.size()); + } + + @Test + public void testValidateWorkflowForOptionalIterationParam() { + WorkflowValidator workflowValidator = new WorkflowValidatorImpl(); + List validationErrors = + workflowValidator + .setSource( + "{\n" + + "\"id\": \"checkInbox\",\n" + + " \"name\": \"Check Inbox Workflow\",\n" + + "\"description\": \"Periodically Check Inbox\",\n" + + "\"version\": \"1.0\",\n" + + "\"start\": \"CheckInbox\",\n" + + "\"functions\": [\n" + + "\n" + + "],\n" + + "\"states\": [\n" + + " {\n" + + " \"name\": \"CheckInbox\",\n" + + " \"type\": \"operation\",\n" + + " \"actionMode\": \"sequential\",\n" + + " \"actions\": [\n" + + " {\n" + + " \"functionRef\": {\n" + + " \"refName\": \"checkInboxFunction\"\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"transition\": {\n" + + " \"nextState\": \"SendTextForHighPrioriry\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"name\": \"SendTextForHighPrioriry\",\n" + + " \"type\": \"foreach\",\n" + + " \"inputCollection\": \"${ .message }\",\n" + + " \"end\": {\n" + + " \"kind\": \"default\"\n" + + " }\n" + + " }\n" + + "]\n" + + "}") + .validate(); + + Assertions.assertNotNull(validationErrors); + Assertions.assertEquals( + 1, + validationErrors.size()); // validation error raised for functionref not for iterationParam + } + + @Test + public void testMissingFunctionRefForCallbackState() { + WorkflowValidator workflowValidator = new WorkflowValidatorImpl(); + List validationErrors = + workflowValidator + .setSource( + "{\n" + + " \"id\": \"callbackstatemissingfuncref\",\n" + + " \"version\": \"1.0\",\n" + + " \"specVersion\": \"0.8\",\n" + + " \"name\": \"Callback State Test\",\n" + + " \"start\": \"CheckCredit\",\n" + + " \"states\": [\n" + + " {\n" + + " \"name\": \"CheckCredit\",\n" + + " \"type\": \"callback\",\n" + + " \"action\": {\n" + + " \"functionRef\": {\n" + + " \"refName\": \"callCreditCheckMicroservice\",\n" + + " \"arguments\": {\n" + + " \"customer\": \"${ .customer }\"\n" + + " }\n" + + " }\n" + + " },\n" + + " \"eventRef\": \"CreditCheckCompletedEvent\",\n" + + " \"timeouts\": {\n" + + " \"stateExecTimeout\": \"PT15M\"\n" + + " },\n" + + " \"end\": true\n" + + " }\n" + + " ]\n" + + "}") + .validate(); + + Assertions.assertNotNull(validationErrors); + Assertions.assertEquals(2, validationErrors.size()); + Assertions.assertEquals( + "CallbackState event ref does not reference a defined workflow event definition", + validationErrors.get(0).getMessage()); + Assertions.assertEquals( + "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