diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f23e469e..a211cd9e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1,2 @@ -* @tsurdilo @manuelstein @ricardozanini \ No newline at end of file +* @ricardozanini +* @fjtirado diff --git a/.github/OWNERS b/.github/OWNERS index da3ddc3f..378a8d78 100644 --- a/.github/OWNERS +++ b/.github/OWNERS @@ -1,10 +1,9 @@ reviewers: - - tsurdilo - - manuelstein - ricardozanini + - manick02 + - fjtirado approvers: - - tsurdilo - - manuelstein - ricardozanini + - fjtirado labels: - - sig/contributor-experience \ No newline at end of file + - sig/contributor-experience diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..07a00d28 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,31 @@ +version: 2 +updates: + # Updates for main branch + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "weekly" + assignees: + - ricardozanini + - fjtirado + target-branch: "main" + + # Updates for 4.x branch + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "weekly" + assignees: + - ricardozanini + - fjtirado + target-branch: "4.x" + + # Updates for 5.x branch + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "weekly" + assignees: + - ricardozanini + - fjtirado + target-branch: "5.x" diff --git a/.github/project.yml b/.github/project.yml new file mode 100644 index 00000000..1d03de1c --- /dev/null +++ b/.github/project.yml @@ -0,0 +1,4 @@ +# Retriggering release again +release: + current-version: 7.1.0.Final + next-version: 8.0.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..9449b5a1 100644 --- a/.github/workflows/maven-verify.yml +++ b/.github/workflows/maven-verify.yml @@ -1,7 +1,7 @@ # 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: @@ -10,21 +10,26 @@ on: pull_request: branches: - main + 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 \ + -pl ",!fluent/agentic" \ + -am + + - name: Verify Examples with Maven + run: | + mvn -B -f examples/pom.xml clean install verify \ No newline at end of file diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml new file mode 100644 index 00000000..9d46ce2a --- /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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..cb4ec5eb --- /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 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/.muse.toml b/.muse.toml deleted file mode 100644 index 3e1c5830..00000000 --- a/.muse.toml +++ /dev/null @@ -1,5 +0,0 @@ -# Ignore results from target directory -ignoreFiles = """ -**/target/generated-sources/src/main/java/io/serverlessworkflow/api/states/DefaultState.java -**/target/generated-sources/src/main/java/io/serverlessworkflow/api/error/Error.java -""" \ No newline at end of file diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 3c807a8c..a7a6b525 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -1,3 +1,5 @@ # Serverless Workflow Java SDK Maintainers -* [Tihomir Surdilovic](https://github.com/tsurdilo) \ No newline at end of file +* [Francisco Javier Tirado Sarti](https://github.com/fjtirado) +* [Manick Sundaram](https://github.com/manick02) +* [Ricardo Zanini](https://github.com/ricardozanini) diff --git a/README.md b/README.md index 6f58269c..caf87812 100644 --- a/README.md +++ b/README.md @@ -3,27 +3,40 @@ # Serverless Workflow Specification - Java SDK -Provides the Java API/SPI and Model Validation for the [Serverless Workflow Specification](https://github.com/serverlessworkflow/specification) +Provides the Java API for the [Serverless Workflow Specification](https://github.com/serverlessworkflow/specification) With the SDK you can: -* Parse workflow JSON and YAML definitions -* Programmatically build workflow definitions -* Validate workflow definitions (both schema and workflow integrity validation) -* Generate workflow diagram (SVG) -Serverless Workflow Java SDK is **not** a workflow runtime implementation but can be used by Java runtime implementations -to parse and validate workflow definitions as well as generate the workflow diagram (SVG). +* Read workflow JSON and YAML definitions +* Write workflow definitions in JSON and YAML formats. +* Test your workflow definitions using the reference implementation. -### Status + +## Status | Latest Releases | Conformance to spec version | | :---: | :---: | -| [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) | +| [7.0.0.Final](https://github.com/serverlessworkflow/sdk-java/releases/tag/7.0.0.Final) | [v1.0.0](https://github.com/serverlessworkflow/specification/tree/1.0.x) | +| [5.0.0.Final](https://github.com/serverlessworkflow/sdk-java/releases/tag/5.0.0.Final) | [v0.8](https://github.com/serverlessworkflow/specification/tree/0.8.x) | +| [4.0.5.Final](https://github.com/serverlessworkflow/sdk-java/releases/tag/4.0.5.Final) | [v0.8](https://github.com/serverlessworkflow/specification/tree/0.8.x) | +| [3.0.0.Final](https://github.com/serverlessworkflow/sdk-java/releases/tag/3.0.0.Final) | [v0.7](https://github.com/serverlessworkflow/specification/tree/0.7.x) | +| [2.0.0.Final](https://github.com/serverlessworkflow/sdk-java/releases/tag/2.0.0.Final) | [v0.6](https://github.com/serverlessworkflow/specification/tree/0.6.x) | +| [1.0.3.Final](https://github.com/serverlessworkflow/sdk-java/releases/tag/1.0.3.Final) | [v0.5](https://github.com/serverlessworkflow/specification/tree/0.5.x) | + +Note that 6.0.0.Final, which will be the one for specification version 0.9, is skipped intentionally in case someone want to work on it. + +## JDK Version + +| SDK Version | JDK Version | +| :---: | :---: | +| 7.0.0 and after | 17 | +| 5.0.0 and after | 11 | +| 4.0.x and before | 8 | + +## Getting Started -### Getting Started -#### Building locally +### Building SNAPSHOT locally To build project and run tests locally: @@ -32,249 +45,98 @@ git clone https://github.com/serverlessworkflow/sdk-java.git mvn clean install ``` -To use it in your projects you can: - -#### Maven projects: - -a) Add the following repository to your pom.xml `repositories` section: +The project uses [Google's code styleguide](https://google.github.io/styleguide/javaguide.html). +Your changes should be automatically formatted during the build. -```xml - - oss.sonatype.org-snapshot - http://oss.sonatype.org/content/repositories/snapshots - - false - - - true - - -``` +### Maven projects: -b) Add the following dependencies to your pom.xml `dependencies` section: +Add the following dependencies to your pom.xml `dependencies` section: ```xml io.serverlessworkflow serverlessworkflow-api - 3.0.0-SNAPSHOT - - - - io.serverlessworkflow - serverlessworkflow-spi - 3.0.0-SNAPSHOT - - - - io.serverlessworkflow - serverlessworkflow-validation - 3.0.0-SNAPSHOT - - - - io.serverlessworkflow - serverlessworkflow-diagram - 3.0.0-SNAPSHOT + 7.0.0.Final ``` -#### Gradle projects: +### Gradle projects: -a) Add the following repositories to your build.gradle `repositories` section: + Add the following dependencies to your build.gradle `dependencies` section: ```text -maven { url "https://oss.sonatype.org/content/repositories/snapshots" } +implementation("io.serverlessworkflow:serverlessworkflow-api:7.0.0.Final") ``` -b) Add the following dependencies to your build.gradle `dependencies` section: +## How to Use -```text -implementation("io.serverlessworkflow:serverlessworkflow-api:3.0.0-SNAPSHOT") -implementation("io.serverlessworkflow:serverlessworkflow-spi:3.0.0-SNAPSHOT") -implementation("io.serverlessworkflow:serverlessworkflow-validation:3.0.0-SNAPSHOT") -implementation("io.serverlessworkflow:serverlessworkflow-diagram:3.0.0-SNAPSHOT") -``` +There are, roughly speaking, two kind of users of this SDK: + * Those ones interested on implementing their own runtime using Java. + * Those ones interested on using the provided runtime reference implementation. -### How to Use +### Implementing your own runtime -#### Creating from JSON/YAML source +For those ones interested on implementing their own runtime, this SDK provides an easy way to load an in memory representation of a given workflow definition. +This in-memory representation consists of a hierarchy of POJOS directly generated from the Serverless Workflow specification [schema](api/src/main/resources/schema/workflow.yaml), which ensures the internal representation is aligned with the specification schema. The root of the hierarchy is `io.serverlessworkflow.api.types.Workflow` class -You can create a Workflow instance from JSON/YAML source: +### Reading workflow definition from JSON/YAML source -Let's say you have a simple YAML based workflow definition: - -```yaml -id: greeting -version: '1.0' -name: Greeting Workflow -start: Greet -description: Greet Someone -functions: - - name: greetingFunction - operation: file://myapis/greetingapis.json#greeting -states: -- name: Greet - type: operation - actions: - - functionRef: - refName: greetingFunction - arguments: - name: "${ .greet.name }" - actionDataFilter: - results: "${ .payload.greeting }" - stateDataFilter: - output: "${ .greeting }" - end: true -``` - -To parse it and create a Workflow intance you can do: - -``` java -Workflow workflow = Workflow.fromSource(source); -``` +You can read a Workflow definition from JSON/YAML source: -where 'source' is the above mentioned YAML definition. +Let's say you have a simple YAML based workflow definition in a file name `simple.yaml` located in your working dir: -The fromSource static method can take in definitions in both JSON and YAML formats. - -Once you have the Workflow instance you can use its API to inspect it, for example: - -``` java -assertNotNull(workflow); -assertEquals("greeting", workflow.getId()); -assertEquals("Greeting Workflow", workflow.getName()); - -assertNotNull(workflow.getFunctions()); -assertEquals(1, workflow.getFunctions().size()); -assertEquals("greetingFunction", workflow.getFunctions().get(0).getName()); - -assertNotNull(workflow.getStates()); -assertEquals(1, workflow.getStates().size()); -assertTrue(workflow.getStates().get(0) instanceof OperationState); - -OperationState operationState = (OperationState) workflow.getStates().get(0); -assertEquals("Greet", operationState.getName()); -assertEquals(DefaultState.Type.OPERATION, operationState.getType()); -... -``` - -#### Using builder API - -You can also programmatically create Workflow instances, for example: - -``` java -Workflow workflow = new Workflow() - .withId("test-workflow") - .withName("test-workflow-name") - .withVersion("1.0") - .withStart(new Start().withStateName("MyDelayState")) - .withFunctions(new Functions(Arrays.asList( - new FunctionDefinition().withName("testFunction") - .withOperation("testSwaggerDef#testOperationId"))) - ) - .withStates(Arrays.asList( - new DelayState().withName("MyDelayState").withType(DELAY) - .withTimeDelay("PT1M") - .withEnd( - new End().withTerminate(true) - ) - ) - ); -``` - -This will create a test workflow that defines an event, a function and a single Delay State. - -You can use the workflow instance to get its JSON/YAML definition as well: +```yaml +document: + dsl: 1.0.0-alpha1 + namespace: default + name: implicit-sequence +do: + setRed: + set: + colors: '${ .colors + [ "red" ] }' + setGreen: + set: + colors: '${ .colors + [ "green" ] }' + setBlue: + set: + colors: '${ .colors + [ "blue" ] }' -``` java -assertNotNull(Workflow.toJson(testWorkflow)); -assertNotNull(Workflow.toYaml(testWorkflow)); ``` -#### Using Workflow Validation - -Validation allows you to perform Json Schema validation against the JSON/YAML workflow definitions. -Once you have a `Workflow` instance, you can also run integrity checks. - -You can validate a Workflow JSON/YAML definition to get validation errors: +To parse it and get a Workflow instance you can do: ``` java -WorkflowValidator workflowValidator = new WorkflowValidatorImpl(); -List validationErrors = workflowValidator.setSource("WORKFLOW_MODEL_JSON/YAML").validate(); -``` - -Where `WORKFLOW_MODEL_JSON/YAML` is the actual workflow model JSON or YAML definition. -Or you can just check if it is valid (without getting specific errors): - -``` java -WorkflowValidator workflowValidator = new WorkflowValidatorImpl(); -boolean isValidWorkflow = workflowValidator.setSource("WORKFLOW_MODEL_JSON/YAML").isValid(); +try (InputStream in = new FileInputStream("simple.yaml")) { + Workflow workflow = WorkflowReader.readWorkflow (in, WorkflowFormat.YAML); + // Once you have the Workflow instance you can use its API to inspect it +} ``` - -If you build your Workflow programmatically, you can validate it as well: +By default, Workflows are not validated against the schema (performance being the priority). If you want to enable validation, you can do that by using: ``` java -Workflow workflow = new Workflow() - .withId("test-workflow") - .withVersion("1.0") - .withStart(new Start().withStateName("MyDelayState")) - .withStates(Arrays.asList( - new DelayState().withName("MyDelayState").withType(DefaultState.Type.DELAY) - .withTimeDelay("PT1M") - .withEnd( - new End().withTerminate(true) - ) - )); -); - -WorkflowValidator workflowValidator = new WorkflowValidatorImpl(); -List validationErrors = workflowValidator.setWorkflow(workflow).validate(); +try (InputStream in = new FileInputStream("simple.yaml")) { + Workflow workflow = WorkflowReader.validation().readWorkflow (in, WorkflowFormat.YAML); + // Once you have the Workflow instance you can use its API to inspect it +} ``` -#### Building Workflow Diagram +For additional reading helper methods, including the one to read a workflow definition from classpath, check [WorkflowReader](api/src/main/java/io/serverlessworkflow/api/WorkflowReader.java) class. -Given a valid workflow definition (JSON/YAML) or a Workflow object you can build the workflow diagram SVG. -The generated diagram SVG uses [PlantUML](https://plantuml.com/) state diagram visualization and can be embedded inside your -tooling or web pages, or any SVG viewer. +### Writing workflow definition to a JSON/YAML target -You can build the workflow diagram SVG with the following code: +Given a Workflow instance, you can store it using JSON or YAML format. +For example, to store a workflow using json format in a file called `simple.json`, you write ``` java -Workflow workflow = Workflow.fromSource(source); - -WorkflowDiagram workflowDiagram = new WorkflowDiagramImpl(); -workflowDiagram.setWorkflow(workflow); +try (OutputStream out = new FileOutputStream("simple.json")) { + WorkflowWriter.writeWorkflow(out, workflow, WorkflowFormat.JSON); +} -String diagramSVG = workflowDiagram.getSvgDiagram(); ``` +For additional writing helper methods, check [WorkflowWriter](api/src/main/java/io/serverlessworkflow/api/WorkflowWriter.java) class. -`diagramSVG` includes the diagram SVG source which you can then decide to save to a file, -print, or process further. - -By default the diagram legend is now shown. If you want to enable it you can do: - -``` java -Workflow workflow = Workflow.fromSource(source); - -WorkflowDiagram workflowDiagram = new WorkflowDiagramImpl(); -workflowDiagram.setWorkflow(workflow) - .showLegend(true); - -String diagramSVG = workflowDiagram.getSvgDiagram(); -``` - -Here are some generated diagrams from the specification examples (with legend enabled): - -1. [Job Monitoring Example](https://github.com/serverlessworkflow/specification/blob/master/examples/examples.md#Monitor-Job-Example) -

-Job Monitoring Example Diagram -

- +### Reference implementation -2. [Send CloudEvent on Workflow completion Example](https://github.com/serverlessworkflow/specification/blob/master/examples/examples.md#send-cloudevent-on-workfow-completion-example) -

-Send Cloud Event on Workflow complation -

+The reference implementation provides a ready-to-use runtime that supports the Serverless Workflow Specification. It includes a workflow execution engine, validation utilities, and illustrative examples to help you quickly test and deploy your workflows. For details on usage, configuration, and supported features, see [readme](impl/README.md). diff --git a/annotations/pom.xml b/annotations/pom.xml new file mode 100644 index 00000000..6e583ab9 --- /dev/null +++ b/annotations/pom.xml @@ -0,0 +1,10 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-parent + 8.0.0-SNAPSHOT + + Serverless Workflow :: Annotations + serverlessworkflow-annotations + \ No newline at end of file diff --git a/annotations/src/main/java/io/serverlessworkflow/annotations/AdditionalProperties.java b/annotations/src/main/java/io/serverlessworkflow/annotations/AdditionalProperties.java new file mode 100644 index 00000000..69a3b023 --- /dev/null +++ b/annotations/src/main/java/io/serverlessworkflow/annotations/AdditionalProperties.java @@ -0,0 +1,26 @@ +/* + * 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.annotations; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target(TYPE) +public @interface AdditionalProperties {} diff --git a/annotations/src/main/java/io/serverlessworkflow/annotations/Item.java b/annotations/src/main/java/io/serverlessworkflow/annotations/Item.java new file mode 100644 index 00000000..f42a86d5 --- /dev/null +++ b/annotations/src/main/java/io/serverlessworkflow/annotations/Item.java @@ -0,0 +1,26 @@ +/* + * 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.annotations; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target(TYPE) +public @interface Item {} diff --git a/annotations/src/main/java/io/serverlessworkflow/annotations/ItemKey.java b/annotations/src/main/java/io/serverlessworkflow/annotations/ItemKey.java new file mode 100644 index 00000000..ea75094d --- /dev/null +++ b/annotations/src/main/java/io/serverlessworkflow/annotations/ItemKey.java @@ -0,0 +1,26 @@ +/* + * 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.annotations; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target(METHOD) +public @interface ItemKey {} diff --git a/annotations/src/main/java/io/serverlessworkflow/annotations/ItemValue.java b/annotations/src/main/java/io/serverlessworkflow/annotations/ItemValue.java new file mode 100644 index 00000000..2aaf09e6 --- /dev/null +++ b/annotations/src/main/java/io/serverlessworkflow/annotations/ItemValue.java @@ -0,0 +1,26 @@ +/* + * 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.annotations; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target(METHOD) +public @interface ItemValue {} diff --git a/annotations/src/main/java/io/serverlessworkflow/annotations/OneOfSetter.java b/annotations/src/main/java/io/serverlessworkflow/annotations/OneOfSetter.java new file mode 100644 index 00000000..d67f0292 --- /dev/null +++ b/annotations/src/main/java/io/serverlessworkflow/annotations/OneOfSetter.java @@ -0,0 +1,28 @@ +/* + * 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.annotations; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target(METHOD) +public @interface OneOfSetter { + Class value(); +} diff --git a/api/src/main/java/io/serverlessworkflow/api/interfaces/SwitchCondition.java b/annotations/src/main/java/io/serverlessworkflow/annotations/OneOfValueProvider.java similarity index 86% rename from api/src/main/java/io/serverlessworkflow/api/interfaces/SwitchCondition.java rename to annotations/src/main/java/io/serverlessworkflow/annotations/OneOfValueProvider.java index 3d07a4dd..d275ff9d 100644 --- a/api/src/main/java/io/serverlessworkflow/api/interfaces/SwitchCondition.java +++ b/annotations/src/main/java/io/serverlessworkflow/annotations/OneOfValueProvider.java @@ -1,20 +1,20 @@ /* * 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.interfaces; +package io.serverlessworkflow.annotations; -public interface SwitchCondition { +public interface OneOfValueProvider { + T get(); } diff --git a/annotations/src/main/java/io/serverlessworkflow/annotations/Union.java b/annotations/src/main/java/io/serverlessworkflow/annotations/Union.java new file mode 100644 index 00000000..e6cc4ecb --- /dev/null +++ b/annotations/src/main/java/io/serverlessworkflow/annotations/Union.java @@ -0,0 +1,28 @@ +/* + * 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.annotations; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target(ElementType.TYPE) +public @interface Union { + Class[] value(); +} diff --git a/api/.gitignore b/api/.gitignore index d4dfde66..c7d1122c 100644 --- a/api/.gitignore +++ b/api/.gitignore @@ -28,4 +28,5 @@ target/ build/ ### VS Code ### -.vscode/ \ No newline at end of file +.vscode/ +/.checkstyle diff --git a/api/pom.xml b/api/pom.xml index a2ac65f7..8b7e32a9 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -1,54 +1,51 @@ - + 4.0.0 io.serverlessworkflow serverlessworkflow-parent - 3.0.0-SNAPSHOT + 8.0.0-SNAPSHOT serverlessworkflow-api Serverless Workflow :: API - ${project.parent.version} jar Java SDK for Serverless Workflow Specification - org.slf4j - slf4j-api + io.serverlessworkflow + serverlessworkflow-types + ${project.version} - org.slf4j - jcl-over-slf4j + io.serverlessworkflow + serverlessworkflow-serialization + ${project.version} - com.fasterxml.jackson.core - jackson-core + org.slf4j + slf4j-api - com.fasterxml.jackson.core - jackson-databind + com.networknt + json-schema-validator com.fasterxml.jackson.dataformat jackson-dataformat-yaml + - javax.validation - validation-api + org.hibernate.validator + hibernate-validator + runtime - org.json - json + org.glassfish.expressly + expressly + runtime - - com.github.erosb - everit-json-schema - - org.junit.jupiter @@ -65,6 +62,11 @@ junit-jupiter-params test + + org.assertj + assertj-core + test + org.mockito mockito-core @@ -75,43 +77,48 @@ logback-classic test - - org.assertj - assertj-core - test - - - - - - org.jsonschema2pojo - jsonschema2pojo-maven-plugin + + + + io.serverlessworkflow + serverless-workflow-jackson-generator + ${project.version} + + + io.serverlessworkflow.api.types + io.serverlessworkflow.api.types.jackson + + + + + generate + + generate-sources + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.6.1 + + + add-mixin + generate-sources + + add-source + - ${basedir}/src/main/resources/schema - io.serverlessworkflow.api.types - ${project.build.directory}/generated-sources/src/main/java - true - true - false - false - false - true - true - true - 1.8 - true + + ${project.build.directory}/generated-sources/jacksonmixinpojo + - - - - generate - - generate-sources - - - - - - + + + + + + diff --git a/api/src/main/java/io/serverlessworkflow/api/DirectReader.java b/api/src/main/java/io/serverlessworkflow/api/DirectReader.java new file mode 100644 index 00000000..83fe0550 --- /dev/null +++ b/api/src/main/java/io/serverlessworkflow/api/DirectReader.java @@ -0,0 +1,44 @@ +/* + * 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; + +import io.serverlessworkflow.api.types.Workflow; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; + +class DirectReader implements WorkflowReaderOperations { + + @Override + public Workflow read(InputStream input, WorkflowFormat format) throws IOException { + return format.mapper().readValue(input, Workflow.class); + } + + @Override + public Workflow read(Reader input, WorkflowFormat format) throws IOException { + return format.mapper().readValue(input, Workflow.class); + } + + @Override + public Workflow read(byte[] input, WorkflowFormat format) throws IOException { + return format.mapper().readValue(input, Workflow.class); + } + + @Override + public Workflow read(String input, WorkflowFormat format) throws IOException { + return format.mapper().readValue(input, Workflow.class); + } +} diff --git a/api/src/main/java/io/serverlessworkflow/api/ObjectMapperFactory.java b/api/src/main/java/io/serverlessworkflow/api/ObjectMapperFactory.java new file mode 100644 index 00000000..25a3f2a2 --- /dev/null +++ b/api/src/main/java/io/serverlessworkflow/api/ObjectMapperFactory.java @@ -0,0 +1,61 @@ +/* + * 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; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature; +import io.serverlessworkflow.api.types.jackson.JacksonMixInModule; +import io.serverlessworkflow.serialization.BeanDeserializerModifierWithValidation; +import io.serverlessworkflow.serialization.URIDeserializer; +import io.serverlessworkflow.serialization.URISerializer; +import java.net.URI; + +class ObjectMapperFactory { + + private static final ObjectMapper jsonMapper = configure(new ObjectMapper()); + + private static final ObjectMapper yamlMapper = + configure(new ObjectMapper(new YAMLFactory().enable(Feature.MINIMIZE_QUOTES))); + + public static final ObjectMapper jsonMapper() { + return jsonMapper; + } + + public static final ObjectMapper yamlMapper() { + return yamlMapper; + } + + private static ObjectMapper configure(ObjectMapper mapper) { + SimpleModule validationModule = new SimpleModule(); + validationModule.addDeserializer(URI.class, new URIDeserializer()); + validationModule.addSerializer(URI.class, new URISerializer()); + validationModule.setDeserializerModifier(new BeanDeserializerModifierWithValidation()); + + return mapper + .setSerializationInclusion(Include.NON_NULL) + .configure(SerializationFeature.INDENT_OUTPUT, true) + .configure(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, false) + .configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false) + .registerModule(validationModule) + .registerModule(new JacksonMixInModule()); + } + + private ObjectMapperFactory() {} +} diff --git a/api/src/main/java/io/serverlessworkflow/api/ValidationReader.java b/api/src/main/java/io/serverlessworkflow/api/ValidationReader.java new file mode 100644 index 00000000..25481d5c --- /dev/null +++ b/api/src/main/java/io/serverlessworkflow/api/ValidationReader.java @@ -0,0 +1,79 @@ +/* + * 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; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.InputFormat; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.SchemaValidatorsConfig; +import com.networknt.schema.SpecVersion.VersionFlag; +import com.networknt.schema.ValidationMessage; +import io.serverlessworkflow.api.types.Workflow; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.UncheckedIOException; +import java.util.Set; +import java.util.stream.Collectors; + +class ValidationReader implements WorkflowReaderOperations { + private final JsonSchema schemaObject; + + ValidationReader() { + try (InputStream input = + Thread.currentThread() + .getContextClassLoader() + .getResourceAsStream("schema/workflow.yaml")) { + this.schemaObject = + JsonSchemaFactory.getInstance(VersionFlag.V7) + .getSchema(input, InputFormat.YAML, SchemaValidatorsConfig.builder().build()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public Workflow read(InputStream input, WorkflowFormat format) throws IOException { + return validate(format.mapper().readValue(input, JsonNode.class), format); + } + + @Override + public Workflow read(Reader input, WorkflowFormat format) throws IOException { + return validate(format.mapper().readValue(input, JsonNode.class), format); + } + + @Override + public Workflow read(byte[] input, WorkflowFormat format) throws IOException { + return validate(format.mapper().readValue(input, JsonNode.class), format); + } + + @Override + public Workflow read(String input, WorkflowFormat format) throws IOException { + return validate(format.mapper().readValue(input, JsonNode.class), format); + } + + private Workflow validate(JsonNode value, WorkflowFormat format) { + Set validationErrors = schemaObject.validate(value); + if (!validationErrors.isEmpty()) { + throw new IllegalArgumentException( + validationErrors.stream() + .map(ValidationMessage::toString) + .collect(Collectors.joining("\n"))); + } + return format.mapper().convertValue(value, Workflow.class); + } +} diff --git a/api/src/main/java/io/serverlessworkflow/api/WorkflowFormat.java b/api/src/main/java/io/serverlessworkflow/api/WorkflowFormat.java new file mode 100644 index 00000000..7ca2cc27 --- /dev/null +++ b/api/src/main/java/io/serverlessworkflow/api/WorkflowFormat.java @@ -0,0 +1,74 @@ +/* + * 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; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.nio.file.Path; + +/** + * Enum representing the supported formats for Serverless Workflow definitions. + * + *

Provides utility methods to determine the format based on file name or path, and to access the + * corresponding {@link ObjectMapper} for serialization and deserialization. + */ +public enum WorkflowFormat { + /** JSON format for workflow definitions. */ + JSON(ObjectMapperFactory.jsonMapper()), + + /** YAML format for workflow definitions. */ + YAML(ObjectMapperFactory.yamlMapper()); + + private final ObjectMapper mapper; + + /** + * Determines the {@link WorkflowFormat} from a file path by inspecting its file extension. + * + * @param path the file path to inspect + * @return the corresponding {@link WorkflowFormat} + */ + public static WorkflowFormat fromPath(Path path) { + return fromFileName(path.getFileName().toString()); + } + + /** + * Determines the {@link WorkflowFormat} from a file name by inspecting its extension. Returns + * {@code JSON} if the file name ends with ".json", otherwise returns {@code YAML}. + * + * @param fileName the file name to inspect + * @return the corresponding {@link WorkflowFormat} + */ + public static WorkflowFormat fromFileName(String fileName) { + return fileName.endsWith(".json") ? JSON : YAML; + } + + /** + * Constructs a {@link WorkflowFormat} with the specified {@link ObjectMapper}. + * + * @param mapper the object mapper for this format + */ + private WorkflowFormat(ObjectMapper mapper) { + this.mapper = mapper; + } + + /** + * Returns the {@link ObjectMapper} associated with this workflow format. + * + * @return the object mapper for this format + */ + public ObjectMapper mapper() { + return mapper; + } +} diff --git a/api/src/main/java/io/serverlessworkflow/api/WorkflowReader.java b/api/src/main/java/io/serverlessworkflow/api/WorkflowReader.java new file mode 100644 index 00000000..b4401af0 --- /dev/null +++ b/api/src/main/java/io/serverlessworkflow/api/WorkflowReader.java @@ -0,0 +1,228 @@ +/* + * 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; + +import io.serverlessworkflow.api.types.Workflow; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.nio.file.Files; +import java.nio.file.Path; + +/** Utility class for reading and parsing Serverless Workflow definitions from various sources. */ +public class WorkflowReader { + + /** + * Reads a workflow from an {@link InputStream} using the specified format. + * + * @param input the input stream containing the workflow definition + * @param format the workflow format + * @return the parsed {@link Workflow} + * @throws IOException if an I/O error occurs + */ + public static Workflow readWorkflow(InputStream input, WorkflowFormat format) throws IOException { + return defaultReader().read(input, format); + } + + /** + * Reads a workflow from a {@link Reader} using the specified format. + * + * @param input the reader containing the workflow definition + * @param format the workflow format + * @return the parsed {@link Workflow} + * @throws IOException if an I/O error occurs + */ + public static Workflow readWorkflow(Reader input, WorkflowFormat format) throws IOException { + return defaultReader().read(input, format); + } + + /** + * Reads a workflow from a byte array using the specified format. + * + * @param input the byte array containing the workflow definition + * @param format the workflow format + * @return the parsed {@link Workflow} + * @throws IOException if an I/O error occurs + */ + public static Workflow readWorkflow(byte[] input, WorkflowFormat format) throws IOException { + return defaultReader().read(input, format); + } + + /** + * Reads a workflow from a file path, inferring the format from the file extension. + * + * @param path the path to the workflow file + * @return the parsed {@link Workflow} + * @throws IOException if an I/O error occurs + */ + public static Workflow readWorkflow(Path path) throws IOException { + return readWorkflow(path, WorkflowFormat.fromPath(path), defaultReader()); + } + + /** + * Reads a workflow from a file path using the specified format. + * + * @param path the path to the workflow file + * @param format the workflow format + * @return the parsed {@link Workflow} + * @throws IOException if an I/O error occurs + */ + public static Workflow readWorkflow(Path path, WorkflowFormat format) throws IOException { + return readWorkflow(path, format, defaultReader()); + } + + /** + * Reads a workflow from a string using the specified format. + * + * @param input the string containing the workflow definition + * @param format the workflow format + * @return the parsed {@link Workflow} + * @throws IOException if an I/O error occurs + */ + public static Workflow readWorkflowFromString(String input, WorkflowFormat format) + throws IOException { + return defaultReader().read(input, format); + } + + /** + * Reads a workflow from the classpath, inferring the format from the file name. + * + * @param classpath the classpath location of the workflow file + * @return the parsed {@link Workflow} + * @throws IOException if an I/O error occurs + */ + public static Workflow readWorkflowFromClasspath(String classpath) throws IOException { + return readWorkflowFromClasspath(classpath, defaultReader()); + } + + /** + * Reads a workflow from the classpath using the specified class loader and format. + * + * @param classpath the classpath location of the workflow file + * @param cl the class loader to use + * @param format the workflow format + * @return the parsed {@link Workflow} + * @throws IOException if an I/O error occurs + */ + public static Workflow readWorkflowFromClasspath( + String classpath, ClassLoader cl, WorkflowFormat format) throws IOException { + return readWorkflowFromClasspath(classpath, defaultReader()); + } + + /** + * Reads a workflow from a file path using a custom reader. + * + * @param path the path to the workflow file + * @param reader the custom {@link WorkflowReaderOperations} + * @return the parsed {@link Workflow} + * @throws IOException if an I/O error occurs + */ + public static Workflow readWorkflow(Path path, WorkflowReaderOperations reader) + throws IOException { + return readWorkflow(path, WorkflowFormat.fromPath(path), reader); + } + + /** + * Reads a workflow from a file path using the specified format and custom reader. + * + * @param path the path to the workflow file + * @param format the workflow format + * @param reader the custom {@link WorkflowReaderOperations} + * @return the parsed {@link Workflow} + * @throws IOException if an I/O error occurs + */ + public static Workflow readWorkflow( + Path path, WorkflowFormat format, WorkflowReaderOperations reader) throws IOException { + return reader.read(Files.readAllBytes(path), format); + } + + /** + * Reads a workflow from the classpath using a custom reader. + * + * @param classpath the classpath location of the workflow file + * @param reader the custom {@link WorkflowReaderOperations} + * @return the parsed {@link Workflow} + * @throws IOException if an I/O error occurs + */ + public static Workflow readWorkflowFromClasspath( + String classpath, WorkflowReaderOperations reader) throws IOException { + return readWorkflowFromClasspath( + classpath, + Thread.currentThread().getContextClassLoader(), + WorkflowFormat.fromFileName(classpath), + reader); + } + + /** + * Reads a workflow from the classpath using the specified class loader, format, and custom + * reader. + * + * @param classpath the classpath location of the workflow file + * @param cl the class loader to use + * @param format the workflow format + * @param reader the custom {@link WorkflowReaderOperations} + * @return the parsed {@link Workflow} + * @throws IOException if an I/O error occurs or the resource is not found + */ + public static Workflow readWorkflowFromClasspath( + String classpath, ClassLoader cl, WorkflowFormat format, WorkflowReaderOperations reader) + throws IOException { + try (InputStream in = cl.getResourceAsStream(classpath)) { + if (in == null) { + throw new FileNotFoundException(classpath); + } + return reader.read(in, format); + } + } + + /** + * Returns a {@link WorkflowReaderOperations} instance that performs no validation. + * + * @return a no-validation reader + */ + public static WorkflowReaderOperations noValidation() { + return NoValidationHolder.instance; + } + + /** + * Returns a {@link WorkflowReaderOperations} instance that performs validation. + * + * @return a validation reader + */ + public static WorkflowReaderOperations validation() { + return ValidationHolder.instance; + } + + private static class NoValidationHolder { + private static final WorkflowReaderOperations instance = new DirectReader(); + } + + private static class ValidationHolder { + private static final WorkflowReaderOperations instance = new ValidationReader(); + } + + /** + * Returns the default {@link WorkflowReaderOperations} instance (no validation). + * + * @return the default reader + */ + private static WorkflowReaderOperations defaultReader() { + return NoValidationHolder.instance; + } + + private WorkflowReader() {} +} diff --git a/api/src/main/java/io/serverlessworkflow/api/interfaces/WorkflowValidator.java b/api/src/main/java/io/serverlessworkflow/api/WorkflowReaderOperations.java similarity index 55% rename from api/src/main/java/io/serverlessworkflow/api/interfaces/WorkflowValidator.java rename to api/src/main/java/io/serverlessworkflow/api/WorkflowReaderOperations.java index 0e0e8955..7049aba0 100644 --- a/api/src/main/java/io/serverlessworkflow/api/interfaces/WorkflowValidator.java +++ b/api/src/main/java/io/serverlessworkflow/api/WorkflowReaderOperations.java @@ -1,37 +1,31 @@ /* * 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.interfaces; - -import io.serverlessworkflow.api.Workflow; -import io.serverlessworkflow.api.validation.ValidationError; - -import java.util.List; - -public interface WorkflowValidator { - - WorkflowValidator setWorkflow(Workflow workflow); +package io.serverlessworkflow.api; - WorkflowValidator setSource(String source); +import io.serverlessworkflow.api.types.Workflow; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; - List validate(); +public interface WorkflowReaderOperations { + Workflow read(InputStream input, WorkflowFormat format) throws IOException; - boolean isValid(); + Workflow read(Reader input, WorkflowFormat format) throws IOException; - WorkflowValidator setSchemaValidationEnabled(boolean schemaValidationEnabled); + Workflow read(byte[] input, WorkflowFormat format) throws IOException; - WorkflowValidator reset(); -} \ No newline at end of file + Workflow read(String input, WorkflowFormat format) throws IOException; +} diff --git a/api/src/main/java/io/serverlessworkflow/api/WorkflowWriter.java b/api/src/main/java/io/serverlessworkflow/api/WorkflowWriter.java new file mode 100644 index 00000000..3285cff4 --- /dev/null +++ b/api/src/main/java/io/serverlessworkflow/api/WorkflowWriter.java @@ -0,0 +1,119 @@ +/* + * 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; + +import io.serverlessworkflow.api.types.Workflow; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Utility class for writing Serverless Workflow definitions to various outputs and formats. + * + *

This class provides static methods to serialize {@link Workflow} objects to files, streams, + * writers, byte arrays, or strings in either JSON or YAML format. The format is determined by the + * {@link WorkflowFormat} parameter or inferred from file extensions. + */ +public class WorkflowWriter { + + /** + * Writes a {@link Workflow} to the given {@link OutputStream} in the specified format. + * + * @param output the output stream to write the workflow to + * @param workflow the workflow object to serialize + * @param format the format to use (JSON or YAML) + * @throws IOException if an I/O error occurs during writing + */ + public static void writeWorkflow(OutputStream output, Workflow workflow, WorkflowFormat format) + throws IOException { + format.mapper().writeValue(output, workflow); + } + + /** + * Writes a {@link Workflow} to the given {@link Writer} in the specified format. + * + * @param output the writer to write the workflow to + * @param workflow the workflow object to serialize + * @param format the format to use (JSON or YAML) + * @throws IOException if an I/O error occurs during writing + */ + public static void writeWorkflow(Writer output, Workflow workflow, WorkflowFormat format) + throws IOException { + format.mapper().writeValue(output, workflow); + } + + /** + * Writes a {@link Workflow} to the specified file path, inferring the format from the file + * extension. + * + * @param output the file path to write the workflow to + * @param workflow the workflow object to serialize + * @throws IOException if an I/O error occurs during writing + */ + public static void writeWorkflow(Path output, Workflow workflow) throws IOException { + writeWorkflow(output, workflow, WorkflowFormat.fromPath(output)); + } + + /** + * Writes a {@link Workflow} to the specified file path in the given format. + * + * @param output the file path to write the workflow to + * @param workflow the workflow object to serialize + * @param format the format to use (JSON or YAML) + * @throws IOException if an I/O error occurs during writing + */ + public static void writeWorkflow(Path output, Workflow workflow, WorkflowFormat format) + throws IOException { + try (OutputStream out = Files.newOutputStream(output)) { + writeWorkflow(out, workflow, format); + } + } + + /** + * Serializes a {@link Workflow} to a string in the specified format. + * + * @param workflow the workflow object to serialize + * @param format the format to use (JSON or YAML) + * @return the serialized workflow as a string + * @throws IOException if an error occurs during serialization + */ + public static String workflowAsString(Workflow workflow, WorkflowFormat format) + throws IOException { + return format.mapper().writeValueAsString(workflow); + } + + /** + * Serializes a {@link Workflow} to a byte array in the specified format. + * + * @param workflow the workflow object to serialize + * @param format the format to use (JSON or YAML) + * @return the serialized workflow as a byte array + * @throws IOException if an error occurs during serialization + */ + public static byte[] workflowAsBytes(Workflow workflow, WorkflowFormat format) + throws IOException { + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + writeWorkflow(out, workflow, format); + return out.toByteArray(); + } + } + + // Private constructor to prevent instantiation + private WorkflowWriter() {} +} diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/CronDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/CronDeserializer.java deleted file mode 100644 index 18a81ff6..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/CronDeserializer.java +++ /dev/null @@ -1,72 +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.deserializers; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import io.serverlessworkflow.api.cron.Cron; -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; - -import java.io.IOException; - -public class CronDeserializer extends StdDeserializer { - - private static final long serialVersionUID = 510l; - - @SuppressWarnings("unused") - private WorkflowPropertySource context; - - public CronDeserializer() { - this(Cron.class); - } - - public CronDeserializer(Class vc) { - super(vc); - } - - public CronDeserializer(WorkflowPropertySource context) { - this(Cron.class); - this.context = context; - } - - @Override - public Cron deserialize(JsonParser jp, - DeserializationContext ctxt) throws IOException { - - JsonNode node = jp.getCodec().readTree(jp); - - Cron cron = new Cron(); - - if (!node.isObject()) { - cron.setExpression(node.asText()); - return cron; - } else { - if(node.get("expression") != null) { - cron.setExpression(node.get("expression").asText()); - } - - if(node.get("validUntil") != null) { - cron.setValidUntil(node.get("validUntil").asText()); - } - - return cron; - } - } -} - diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/DataInputSchemaDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/DataInputSchemaDeserializer.java deleted file mode 100644 index 79ec9a68..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/DataInputSchemaDeserializer.java +++ /dev/null @@ -1,64 +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.deserializers; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import io.serverlessworkflow.api.datainputschema.DataInputSchema; -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; -import java.io.IOException; - -public class DataInputSchemaDeserializer extends StdDeserializer { - - private static final long serialVersionUID = 510l; - - public DataInputSchemaDeserializer() { - this(DataInputSchema.class); - } - - public DataInputSchemaDeserializer(Class vc) { - super(vc); - } - - public DataInputSchemaDeserializer(WorkflowPropertySource context) { - this(DataInputSchema.class); - } - - @Override - public DataInputSchema deserialize(JsonParser jp, - DeserializationContext ctxt) throws IOException { - - JsonNode node = jp.getCodec().readTree(jp); - - DataInputSchema dataInputSchema = new DataInputSchema(); - - if (!node.isObject()) { - dataInputSchema.setSchema(node.asText()); - dataInputSchema.setFailOnValidationErrors(true); // default - - return dataInputSchema; - } else { - dataInputSchema.setSchema(node.get("schema").asText()); - dataInputSchema.setFailOnValidationErrors(node.get("failOnValidationErrors").asBoolean()); - - return dataInputSchema; - } - } -} - diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/DefaultStateTypeDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/DefaultStateTypeDeserializer.java deleted file mode 100644 index b2870ee6..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/DefaultStateTypeDeserializer.java +++ /dev/null @@ -1,72 +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.deserializers; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; -import io.serverlessworkflow.api.states.DefaultState; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; - -public class DefaultStateTypeDeserializer extends StdDeserializer { - - private static final long serialVersionUID = 510l; - private static Logger logger = LoggerFactory.getLogger(DefaultStateTypeDeserializer.class); - - private WorkflowPropertySource context; - - public DefaultStateTypeDeserializer() { - this(DefaultState.Type.class); - } - - public DefaultStateTypeDeserializer(WorkflowPropertySource context) { - this(DefaultState.Type.class); - this.context = context; - } - - public DefaultStateTypeDeserializer(Class vc) { - super(vc); - } - - @Override - public DefaultState.Type deserialize(JsonParser jp, - DeserializationContext ctxt) throws IOException { - - String value = jp.getText(); - - if (context != null) { - try { - String result = context.getPropertySource().getProperty(value); - - if (result != null) { - return DefaultState.Type.fromValue(result); - } else { - return DefaultState.Type.fromValue(jp.getText()); - } - } catch (Exception e) { - logger.info("Exception trying to evaluate property: {}", e.getMessage()); - return DefaultState.Type.fromValue(jp.getText()); - } - } else { - return DefaultState.Type.fromValue(jp.getText()); - } - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/EndDefinitionDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/EndDefinitionDeserializer.java deleted file mode 100644 index 4fa3faa5..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/EndDefinitionDeserializer.java +++ /dev/null @@ -1,93 +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.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.end.End; -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; -import io.serverlessworkflow.api.produce.ProduceEvent; -import io.serverlessworkflow.api.start.Start; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -public class EndDefinitionDeserializer extends StdDeserializer { - - private static final long serialVersionUID = 510l; - - @SuppressWarnings("unused") - private WorkflowPropertySource context; - - public EndDefinitionDeserializer() { - this(End.class); - } - - public EndDefinitionDeserializer(Class vc) { - super(vc); - } - - public EndDefinitionDeserializer(WorkflowPropertySource context) { - this(Start.class); - this.context = context; - } - - @Override - public End deserialize(JsonParser jp, - DeserializationContext ctxt) throws IOException { - - ObjectMapper mapper = (ObjectMapper) jp.getCodec(); - JsonNode node = jp.getCodec().readTree(jp); - - End end = new End(); - - if (node.isBoolean()) { - end.setProduceEvents(null); - end.setCompensate(false); - end.setTerminate(false); - return node.asBoolean() ? end : null; - } else { - if(node.get("produceEvents") != null) { - List produceEvents= new ArrayList<>(); - for (final JsonNode nodeEle : node.get("produceEvents")) { - produceEvents.add(mapper.treeToValue(nodeEle, ProduceEvent.class)); - } - end.setProduceEvents(produceEvents); - } - - if(node.get("terminate") != null) { - end.setTerminate(node.get("terminate").asBoolean()); - } else { - end.setTerminate(false); - } - - if(node.get("compensate") != null) { - end.setCompensate(node.get("compensate").asBoolean()); - } else { - end.setCompensate(false); - } - - return end; - - } - - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/EventDefinitionKindDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/EventDefinitionKindDeserializer.java deleted file mode 100644 index 272c0c81..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/EventDefinitionKindDeserializer.java +++ /dev/null @@ -1,70 +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.deserializers; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import io.serverlessworkflow.api.events.EventDefinition; -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; - -public class EventDefinitionKindDeserializer extends StdDeserializer { - private static final long serialVersionUID = 510l; - private static Logger logger = LoggerFactory.getLogger(EventDefinitionKindDeserializer.class); - - private WorkflowPropertySource context; - - public EventDefinitionKindDeserializer() { - this(EventDefinition.Kind.class); - } - - public EventDefinitionKindDeserializer(WorkflowPropertySource context) { - this(EventDefinition.Kind.class); - this.context = context; - } - - public EventDefinitionKindDeserializer(Class vc) { - super(vc); - } - - @Override - public EventDefinition.Kind deserialize(JsonParser jp, - DeserializationContext ctxt) throws IOException { - - String value = jp.getText(); - if (context != null) { - try { - String result = context.getPropertySource().getProperty(value); - - if (result != null) { - return EventDefinition.Kind.fromValue(result); - } else { - return EventDefinition.Kind.fromValue(jp.getText()); - } - } catch (Exception e) { - logger.info("Exception trying to evaluate property: {}", e.getMessage()); - return EventDefinition.Kind.fromValue(jp.getText()); - } - } else { - return EventDefinition.Kind.fromValue(jp.getText()); - } - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/EventsDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/EventsDeserializer.java deleted file mode 100644 index 16de3a83..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/EventsDeserializer.java +++ /dev/null @@ -1,107 +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.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 com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import io.serverlessworkflow.api.events.EventDefinition; -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; -import io.serverlessworkflow.api.utils.Utils; -import io.serverlessworkflow.api.workflow.Events; -import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -public class EventsDeserializer extends StdDeserializer { - - private static final long serialVersionUID = 510l; - private static Logger logger = LoggerFactory.getLogger(EventsDeserializer.class); - - @SuppressWarnings("unused") - private WorkflowPropertySource context; - - public EventsDeserializer() { - this(Events.class); - } - - public EventsDeserializer(Class vc) { - super(vc); - } - - public EventsDeserializer(WorkflowPropertySource context) { - this(Events.class); - this.context = context; - } - - @Override - public Events deserialize(JsonParser jp, - DeserializationContext ctxt) throws IOException { - - ObjectMapper mapper = (ObjectMapper) jp.getCodec(); - JsonNode node = jp.getCodec().readTree(jp); - - Events events = new Events(); - List eventDefs = new ArrayList<>(); - - if (node.isArray()) { - for (final JsonNode nodeEle : node) { - eventDefs.add(mapper.treeToValue(nodeEle, EventDefinition.class)); - } - } 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 refEvents = eventsRefNode.get("events"); - if (refEvents != null) { - for (final JsonNode nodeEle : refEvents) { - eventDefs.add(mapper.treeToValue(nodeEle, EventDefinition.class)); - } - } else { - logger.error("Unable to find event definitions in reference file: {}", eventsFileSrc); - } - - } else { - logger.error("Unable to load event defs reference file: {}", eventsFileSrc); - } - - } - events.setEventDefs(eventDefs); - return events; - - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/ExtensionDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/ExtensionDeserializer.java deleted file mode 100644 index 88db9a1f..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/ExtensionDeserializer.java +++ /dev/null @@ -1,85 +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.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.interfaces.Extension; -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -public class ExtensionDeserializer extends StdDeserializer { - - private WorkflowPropertySource context; - private Map> extensionsMap = new HashMap<>(); - private static Logger logger = LoggerFactory.getLogger(ExtensionDeserializer.class); - - public ExtensionDeserializer() { - this(Extension.class); - } - - public ExtensionDeserializer(Class vc) { - super(vc); - } - - public ExtensionDeserializer(WorkflowPropertySource context) { - this(Extension.class); - this.context = context; - } - - public void addExtension(String extensionId, Class extensionClass) { - this.extensionsMap.put(extensionId, extensionClass); - } - - @Override - public Extension deserialize(JsonParser jp, - DeserializationContext ctxt) throws IOException { - - ObjectMapper mapper = (ObjectMapper) jp.getCodec(); - JsonNode node = jp.getCodec().readTree(jp); - - String extensionId = node.get("extensionid").asText(); - - if (context != null) { - try { - String result = context.getPropertySource().getProperty(extensionId); - - if (result != null) { - extensionId = result; - } - } catch (Exception e) { - logger.info("Exception trying to evaluate property: {}", e.getMessage()); - } - } - - // based on the name return the specific extension impl - if (extensionsMap != null && extensionsMap.containsKey(extensionId)) { - return mapper.treeToValue(node, - extensionsMap.get(extensionId)); - } else { - throw new IllegalArgumentException("Extension handler not registered for: " + extensionId); - } - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/FunctionDefinitionTypeDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/FunctionDefinitionTypeDeserializer.java deleted file mode 100644 index 35db33be..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/FunctionDefinitionTypeDeserializer.java +++ /dev/null @@ -1,71 +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.deserializers; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import io.serverlessworkflow.api.functions.FunctionDefinition; -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; - -public class FunctionDefinitionTypeDeserializer extends StdDeserializer { - - private static final long serialVersionUID = 510l; - private static Logger logger = LoggerFactory.getLogger(FunctionDefinitionTypeDeserializer.class); - - private WorkflowPropertySource context; - - public FunctionDefinitionTypeDeserializer() { - this(FunctionDefinition.Type.class); - } - - public FunctionDefinitionTypeDeserializer(WorkflowPropertySource context) { - this(FunctionDefinition.Type.class); - this.context = context; - } - - public FunctionDefinitionTypeDeserializer(Class vc) { - super(vc); - } - - @Override - public FunctionDefinition.Type deserialize(JsonParser jp, - DeserializationContext ctxt) throws IOException { - - String value = jp.getText(); - if (context != null) { - try { - String result = context.getPropertySource().getProperty(value); - - if (result != null) { - return FunctionDefinition.Type.fromValue(result); - } else { - return FunctionDefinition.Type.fromValue(jp.getText()); - } - } catch (Exception e) { - logger.info("Exception trying to evaluate property: {}", e.getMessage()); - return FunctionDefinition.Type.fromValue(jp.getText()); - } - } else { - return FunctionDefinition.Type.fromValue(jp.getText()); - } - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/FunctionRefDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/FunctionRefDeserializer.java deleted file mode 100644 index c12f98dc..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/FunctionRefDeserializer.java +++ /dev/null @@ -1,74 +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.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.functions.FunctionRef; -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; - -import java.io.IOException; - -public class FunctionRefDeserializer extends StdDeserializer { - - private static final long serialVersionUID = 510l; - - @SuppressWarnings("unused") - private WorkflowPropertySource context; - - public FunctionRefDeserializer() { - this(FunctionRef.class); - } - - public FunctionRefDeserializer(Class vc) { - super(vc); - } - - public FunctionRefDeserializer(WorkflowPropertySource context) { - this(FunctionRef.class); - this.context = context; - } - - @Override - public FunctionRef deserialize(JsonParser jp, - DeserializationContext ctxt) throws IOException { - - ObjectMapper mapper = (ObjectMapper) jp.getCodec(); - JsonNode node = jp.getCodec().readTree(jp); - - FunctionRef functionRef = new FunctionRef(); - - if (!node.isObject()) { - functionRef.setRefName(node.asText()); - functionRef.setArguments(null); - return functionRef; - } else { - if(node.get("arguments") != null) { - functionRef.setArguments(mapper.treeToValue(node.get("arguments"), JsonNode.class)); - } - - if(node.get("refName") != null) { - functionRef.setRefName(node.get("refName").asText()); - } - - return functionRef; - } - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/FunctionsDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/FunctionsDeserializer.java deleted file mode 100644 index 9933936f..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/FunctionsDeserializer.java +++ /dev/null @@ -1,106 +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.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 com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import io.serverlessworkflow.api.functions.FunctionDefinition; -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; -import io.serverlessworkflow.api.utils.Utils; -import io.serverlessworkflow.api.workflow.Functions; -import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -public class FunctionsDeserializer extends StdDeserializer { - - private static final long serialVersionUID = 510l; - private static Logger logger = LoggerFactory.getLogger(FunctionsDeserializer.class); - - @SuppressWarnings("unused") - private WorkflowPropertySource context; - - public FunctionsDeserializer() { - this(Functions.class); - } - - public FunctionsDeserializer(Class vc) { - super(vc); - } - - public FunctionsDeserializer(WorkflowPropertySource context) { - this(Functions.class); - this.context = context; - } - - @Override - public Functions deserialize(JsonParser jp, - DeserializationContext ctxt) throws IOException { - - ObjectMapper mapper = (ObjectMapper) jp.getCodec(); - JsonNode node = jp.getCodec().readTree(jp); - - Functions functions = new Functions(); - List functionDefs = new ArrayList<>(); - if (node.isArray()) { - for (final JsonNode nodeEle : node) { - functionDefs.add(mapper.treeToValue(nodeEle, FunctionDefinition.class)); - } - } else { - 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()); - } - - JsonNode refFunctions = functionsRefNode.get("functions"); - if (refFunctions != null) { - for (final JsonNode nodeEle : refFunctions) { - functionDefs.add(mapper.treeToValue(nodeEle, FunctionDefinition.class)); - } - } else { - logger.error("Unable to find function definitions in reference file: {}", functionsFileSrc); - } - - } else { - logger.error("Unable to load function defs reference file: {}", functionsFileSrc); - } - - } - functions.setFunctionDefs(functionDefs); - return functions; - - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/OnEventsActionModeDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/OnEventsActionModeDeserializer.java deleted file mode 100644 index 44c7f13f..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/OnEventsActionModeDeserializer.java +++ /dev/null @@ -1,71 +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.deserializers; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import io.serverlessworkflow.api.events.OnEvents; -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; - -public class OnEventsActionModeDeserializer extends StdDeserializer { - - private static final long serialVersionUID = 510l; - private static Logger logger = LoggerFactory.getLogger(OnEventsActionModeDeserializer.class); - - private WorkflowPropertySource context; - - public OnEventsActionModeDeserializer() { - this(OnEvents.ActionMode.class); - } - - public OnEventsActionModeDeserializer(WorkflowPropertySource context) { - this(OnEvents.ActionMode.class); - this.context = context; - } - - public OnEventsActionModeDeserializer(Class vc) { - super(vc); - } - - @Override - public OnEvents.ActionMode deserialize(JsonParser jp, - DeserializationContext ctxt) throws IOException { - - String value = jp.getText(); - if (context != null) { - try { - String result = context.getPropertySource().getProperty(value); - - if (result != null) { - return OnEvents.ActionMode.fromValue(result); - } else { - return OnEvents.ActionMode.fromValue(jp.getText()); - } - } catch (Exception e) { - logger.info("Exception trying to evaluate property: {}", e.getMessage()); - return OnEvents.ActionMode.fromValue(jp.getText()); - } - } else { - return OnEvents.ActionMode.fromValue(jp.getText()); - } - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/OperationStateActionModeDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/OperationStateActionModeDeserializer.java deleted file mode 100644 index 31cbee7b..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/OperationStateActionModeDeserializer.java +++ /dev/null @@ -1,71 +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.deserializers; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; -import io.serverlessworkflow.api.states.OperationState; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; - -public class OperationStateActionModeDeserializer extends StdDeserializer { - - private static final long serialVersionUID = 510l; - private static Logger logger = LoggerFactory.getLogger(OperationStateActionModeDeserializer.class); - - private WorkflowPropertySource context; - - public OperationStateActionModeDeserializer() { - this(OperationState.ActionMode.class); - } - - public OperationStateActionModeDeserializer(WorkflowPropertySource context) { - this(OperationState.ActionMode.class); - this.context = context; - } - - public OperationStateActionModeDeserializer(Class vc) { - super(vc); - } - - @Override - public OperationState.ActionMode deserialize(JsonParser jp, - DeserializationContext ctxt) throws IOException { - - String value = jp.getText(); - if (context != null) { - try { - String result = context.getPropertySource().getProperty(value); - - if (result != null) { - return OperationState.ActionMode.fromValue(result); - } else { - return OperationState.ActionMode.fromValue(jp.getText()); - } - } catch (Exception e) { - logger.info("Exception trying to evaluate property: {}", e.getMessage()); - return OperationState.ActionMode.fromValue(jp.getText()); - } - } else { - return OperationState.ActionMode.fromValue(jp.getText()); - } - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/ParallelStateCompletionTypeDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/ParallelStateCompletionTypeDeserializer.java deleted file mode 100644 index 6893ca6c..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/ParallelStateCompletionTypeDeserializer.java +++ /dev/null @@ -1,71 +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.deserializers; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; -import io.serverlessworkflow.api.states.ParallelState; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; - -public class ParallelStateCompletionTypeDeserializer extends StdDeserializer { - - private static final long serialVersionUID = 510l; - private static Logger logger = LoggerFactory.getLogger(ParallelStateCompletionTypeDeserializer.class); - - private WorkflowPropertySource context; - - public ParallelStateCompletionTypeDeserializer() { - this(ParallelState.CompletionType.class); - } - - public ParallelStateCompletionTypeDeserializer(WorkflowPropertySource context) { - this(ParallelState.CompletionType.class); - this.context = context; - } - - public ParallelStateCompletionTypeDeserializer(Class vc) { - super(vc); - } - - @Override - public ParallelState.CompletionType deserialize(JsonParser jp, - DeserializationContext ctxt) throws IOException { - - String value = jp.getText(); - if (context != null) { - try { - String result = context.getPropertySource().getProperty(value); - - if (result != null) { - return ParallelState.CompletionType.fromValue(result); - } else { - return ParallelState.CompletionType.fromValue(jp.getText()); - } - } catch (Exception e) { - logger.info("Exception trying to evaluate property: {}", e.getMessage()); - return ParallelState.CompletionType.fromValue(jp.getText()); - } - } else { - return ParallelState.CompletionType.fromValue(jp.getText()); - } - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/RetriesDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/RetriesDeserializer.java deleted file mode 100644 index 4bb25e15..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/RetriesDeserializer.java +++ /dev/null @@ -1,107 +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.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 com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; -import io.serverlessworkflow.api.retry.RetryDefinition; -import io.serverlessworkflow.api.utils.Utils; -import io.serverlessworkflow.api.workflow.Retries; -import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -public class RetriesDeserializer extends StdDeserializer { - - private static final long serialVersionUID = 510l; - private static Logger logger = LoggerFactory.getLogger(RetriesDeserializer.class); - - @SuppressWarnings("unused") - private WorkflowPropertySource context; - - public RetriesDeserializer() { - this(Retries.class); - } - - public RetriesDeserializer(Class vc) { - super(vc); - } - - public RetriesDeserializer(WorkflowPropertySource context) { - this(Retries.class); - this.context = context; - } - - @Override - public Retries deserialize(JsonParser jp, - DeserializationContext ctxt) throws IOException { - - ObjectMapper mapper = (ObjectMapper) jp.getCodec(); - JsonNode node = jp.getCodec().readTree(jp); - - Retries retries = new Retries(); - List retryDefinitions = new ArrayList<>(); - - if (node.isArray()) { - for (final JsonNode nodeEle : node) { - retryDefinitions.add(mapper.treeToValue(nodeEle, RetryDefinition.class)); - } - } 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 refRetries = retriesRefNode.get("retries"); - if (refRetries != null) { - for (final JsonNode nodeEle : refRetries) { - retryDefinitions.add(mapper.treeToValue(nodeEle, RetryDefinition.class)); - } - } else { - logger.error("Unable to find retries definitions in reference file: {}", retriesFileSrc); - } - - } else { - logger.error("Unable to load retries defs reference file: {}", retriesFileSrc); - } - - } - retries.setRetryDefs(retryDefinitions); - return retries; - - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/ScheduleDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/ScheduleDeserializer.java deleted file mode 100644 index 4fa6c6f2..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/ScheduleDeserializer.java +++ /dev/null @@ -1,78 +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.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.cron.Cron; -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; -import io.serverlessworkflow.api.schedule.Schedule; - -import java.io.IOException; - -public class ScheduleDeserializer extends StdDeserializer { - - private static final long serialVersionUID = 510l; - - @SuppressWarnings("unused") - private WorkflowPropertySource context; - - public ScheduleDeserializer() { - this(Schedule.class); - } - - public ScheduleDeserializer(Class vc) { - super(vc); - } - - public ScheduleDeserializer(WorkflowPropertySource context) { - this(Schedule.class); - this.context = context; - } - - @Override - public Schedule deserialize(JsonParser jp, - DeserializationContext ctxt) throws IOException { - - ObjectMapper mapper = (ObjectMapper) jp.getCodec(); - JsonNode node = jp.getCodec().readTree(jp); - - Schedule schedule = new Schedule(); - - if (!node.isObject()) { - schedule.setInterval(node.asText()); - return schedule; - } else { - if(node.get("interval") != null) { - schedule.setInterval(node.get("interval").asText()); - } - - if(node.get("cron") != null) { - schedule.setCron(mapper.treeToValue(node.get("cron"), Cron.class)); - } - - if(node.get("timezone") != null) { - schedule.setTimezone(node.get("timezone").asText()); - } - - return schedule; - } - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/StartDefinitionDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/StartDefinitionDeserializer.java deleted file mode 100644 index c1048333..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/StartDefinitionDeserializer.java +++ /dev/null @@ -1,78 +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.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.interfaces.WorkflowPropertySource; -import io.serverlessworkflow.api.schedule.Schedule; -import io.serverlessworkflow.api.start.Start; - -import java.io.IOException; - -public class StartDefinitionDeserializer extends StdDeserializer { - - private static final long serialVersionUID = 510l; - - @SuppressWarnings("unused") - private WorkflowPropertySource context; - - public StartDefinitionDeserializer() { - this(Start.class); - } - - public StartDefinitionDeserializer(Class vc) { - super(vc); - } - - public StartDefinitionDeserializer(WorkflowPropertySource context) { - this(Start.class); - this.context = context; - } - - @Override - public Start deserialize(JsonParser jp, - DeserializationContext ctxt) throws IOException { - - ObjectMapper mapper = (ObjectMapper) jp.getCodec(); - JsonNode node = jp.getCodec().readTree(jp); - - Start start = new Start(); - - if (!node.isObject()) { - start.setStateName(node.asText()); - start.setSchedule(null); - return start; - } else { - if(node.get("stateName") != null) { - start.setStateName(node.get("stateName").asText()); - } - - if(node.get("schedule") != null) { - start.setSchedule(mapper.treeToValue(node.get("schedule"), Schedule.class)); - } - - return start; - - } - - } -} - diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/StateDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/StateDeserializer.java deleted file mode 100644 index 4a28b6cc..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/StateDeserializer.java +++ /dev/null @@ -1,111 +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.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.interfaces.State; -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; -import io.serverlessworkflow.api.states.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; - -public class StateDeserializer extends StdDeserializer { - - private static final long serialVersionUID = 510l; - private static Logger logger = LoggerFactory.getLogger(StateDeserializer.class); - - private WorkflowPropertySource context; - - public StateDeserializer() { - this(State.class); - } - - public StateDeserializer(Class vc) { - super(vc); - } - - public StateDeserializer(WorkflowPropertySource context) { - this(State.class); - this.context = context; - } - - @Override - public State deserialize(JsonParser jp, - DeserializationContext ctxt) throws IOException { - - ObjectMapper mapper = (ObjectMapper) jp.getCodec(); - JsonNode node = jp.getCodec().readTree(jp); - String typeValue = node.get("type").asText(); - - if (context != null) { - try { - String result = context.getPropertySource().getProperty(typeValue); - - if (result != null) { - typeValue = result; - } - } catch (Exception e) { - logger.info("Exception trying to evaluate property: {}", e.getMessage()); - } - } - - // based on statetype return the specific state impl - DefaultState.Type type = DefaultState.Type.fromValue(typeValue); - switch (type) { - case EVENT: - return mapper.treeToValue(node, - EventState.class); - case OPERATION: - return mapper.treeToValue(node, - OperationState.class); - case SWITCH: - return mapper.treeToValue(node, - SwitchState.class); - case DELAY: - return mapper.treeToValue(node, - DelayState.class); - case PARALLEL: - return mapper.treeToValue(node, - ParallelState.class); - - case SUBFLOW: - return mapper.treeToValue(node, - SubflowState.class); - - case INJECT: - return mapper.treeToValue(node, - InjectState.class); - - case FOREACH: - return mapper.treeToValue(node, - ForEachState.class); - - case CALLBACK: - return mapper.treeToValue(node, - CallbackState.class); - default: - return mapper.treeToValue(node, - DefaultState.class); - } - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/StringValueDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/StringValueDeserializer.java deleted file mode 100644 index 06836e7f..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/StringValueDeserializer.java +++ /dev/null @@ -1,70 +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.deserializers; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; - -public class StringValueDeserializer extends StdDeserializer { - - private static final long serialVersionUID = 510l; - private static Logger logger = LoggerFactory.getLogger(StringValueDeserializer.class); - - private WorkflowPropertySource context; - - public StringValueDeserializer() { - this(String.class); - } - - public StringValueDeserializer(WorkflowPropertySource context) { - this(String.class); - this.context = context; - } - - public StringValueDeserializer(Class vc) { - super(vc); - } - - @Override - public String deserialize(JsonParser jp, - DeserializationContext ctxt) throws IOException { - - String value = jp.getText(); - if (context != null) { - try { - String result = context.getPropertySource().getProperty(value); - - if (result != null) { - return result; - } else { - return jp.getText(); - } - } catch (Exception e) { - logger.info("Exception trying to evaluate property: {}", e.getMessage()); - return jp.getText(); - } - } else { - return jp.getText(); - } - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/deserializers/TransitionDeserializer.java b/api/src/main/java/io/serverlessworkflow/api/deserializers/TransitionDeserializer.java deleted file mode 100644 index 89d60d6d..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/deserializers/TransitionDeserializer.java +++ /dev/null @@ -1,82 +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.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.interfaces.WorkflowPropertySource; -import io.serverlessworkflow.api.produce.ProduceEvent; -import io.serverlessworkflow.api.transitions.Transition; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; - -public class TransitionDeserializer extends StdDeserializer { - - private static final long serialVersionUID = 510l; - - @SuppressWarnings("unused") - private WorkflowPropertySource context; - - public TransitionDeserializer() { - this(Transition.class); - } - - public TransitionDeserializer(Class vc) { - super(vc); - } - - public TransitionDeserializer(WorkflowPropertySource context) { - this(Transition.class); - this.context = context; - } - - @Override - public Transition deserialize(JsonParser jp, - DeserializationContext ctxt) throws IOException { - - ObjectMapper mapper = (ObjectMapper) jp.getCodec(); - JsonNode node = jp.getCodec().readTree(jp); - - Transition transition = new Transition(); - - if (!node.isObject()) { - transition.setProduceEvents(new ArrayList<>()); - transition.setCompensate(false); - transition.setNextState(node.asText()); - return transition; - } else { - if(node.get("produceEvents") != null) { - transition.setProduceEvents(Arrays.asList(mapper.treeToValue(node.get("produceEvents"), ProduceEvent[].class)) ); - } - - if(node.get("nextState") != null) { - transition.setNextState(node.get("nextState").asText()); - } - - if(node.get("compensate") != null) { - transition.setCompensate(node.get("compensate").asBoolean()); - } - - return transition; - } - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/interfaces/State.java b/api/src/main/java/io/serverlessworkflow/api/interfaces/State.java deleted file mode 100644 index e9931ce6..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/interfaces/State.java +++ /dev/null @@ -1,48 +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.interfaces; - -import io.serverlessworkflow.api.end.End; -import io.serverlessworkflow.api.error.Error; -import io.serverlessworkflow.api.filters.StateDataFilter; -import io.serverlessworkflow.api.start.Start; -import io.serverlessworkflow.api.states.DefaultState.Type; -import io.serverlessworkflow.api.transitions.Transition; - -import java.util.List; -import java.util.Map; - -public interface State { - - String getId(); - - String getName(); - - Type getType(); - - End getEnd(); - - StateDataFilter getStateDataFilter(); - - Transition getTransition(); - - List getOnErrors(); - - String getCompensatedBy(); - - Map getMetadata(); -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/mapper/BaseObjectMapper.java b/api/src/main/java/io/serverlessworkflow/api/mapper/BaseObjectMapper.java deleted file mode 100644 index b454431b..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/mapper/BaseObjectMapper.java +++ /dev/null @@ -1,42 +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.mapper; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; - -public class BaseObjectMapper extends ObjectMapper { - - private WorkflowModule workflowModule; - - public BaseObjectMapper(JsonFactory factory, - WorkflowPropertySource workflowPropertySource) { - super(factory); - - workflowModule = new WorkflowModule(workflowPropertySource); - - configure(SerializationFeature.INDENT_OUTPUT, - true); - registerModule(workflowModule); - } - - public WorkflowModule getWorkflowModule() { - return workflowModule; - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/mapper/WorkflowModule.java b/api/src/main/java/io/serverlessworkflow/api/mapper/WorkflowModule.java deleted file mode 100644 index 5d1c64bb..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/mapper/WorkflowModule.java +++ /dev/null @@ -1,117 +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.mapper; - -import com.fasterxml.jackson.databind.module.SimpleModule; -import io.serverlessworkflow.api.cron.Cron; -import io.serverlessworkflow.api.datainputschema.DataInputSchema; -import io.serverlessworkflow.api.deserializers.*; -import io.serverlessworkflow.api.end.End; -import io.serverlessworkflow.api.events.EventDefinition; -import io.serverlessworkflow.api.events.OnEvents; -import io.serverlessworkflow.api.functions.FunctionDefinition; -import io.serverlessworkflow.api.functions.FunctionRef; -import io.serverlessworkflow.api.interfaces.Extension; -import io.serverlessworkflow.api.interfaces.State; -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; -import io.serverlessworkflow.api.schedule.Schedule; -import io.serverlessworkflow.api.serializers.*; -import io.serverlessworkflow.api.start.Start; -import io.serverlessworkflow.api.states.DefaultState; -import io.serverlessworkflow.api.states.OperationState; -import io.serverlessworkflow.api.states.ParallelState; -import io.serverlessworkflow.api.transitions.Transition; -import io.serverlessworkflow.api.workflow.Events; -import io.serverlessworkflow.api.workflow.Functions; -import io.serverlessworkflow.api.workflow.Retries; - -public class WorkflowModule extends SimpleModule { - - private static final long serialVersionUID = 510l; - - private WorkflowPropertySource workflowPropertySource; - private ExtensionSerializer extensionSerializer; - private ExtensionDeserializer extensionDeserializer; - - public WorkflowModule() { - this(null); - } - - public WorkflowModule(WorkflowPropertySource workflowPropertySource) { - super("workflow-module"); - this.workflowPropertySource = workflowPropertySource; - extensionSerializer = new ExtensionSerializer(); - extensionDeserializer = new ExtensionDeserializer(workflowPropertySource); - addDefaultSerializers(); - addDefaultDeserializers(); - } - - private void addDefaultSerializers() { - addSerializer(new WorkflowSerializer()); - addSerializer(new EventStateSerializer()); - addSerializer(new DelayStateSerializer()); - addSerializer(new OperationStateSerializer()); - addSerializer(new ParallelStateSerializer()); - addSerializer(new SwitchStateSerializer()); - addSerializer(new SubflowStateSerializer()); - addSerializer(new InjectStateSerializer()); - addSerializer(new ForEachStateSerializer()); - addSerializer(new CallbackStateSerializer()); - addSerializer(new StartDefinitionSerializer()); - addSerializer(new EndDefinitionSerializer()); - addSerializer(new TransitionSerializer()); - addSerializer(new FunctionRefSerializer()); - addSerializer(new CronSerializer()); - addSerializer(new ScheduleSerializer()); - addSerializer(extensionSerializer); - } - - private void addDefaultDeserializers() { - addDeserializer(State.class, - new StateDeserializer(workflowPropertySource)); - addDeserializer(String.class, - new StringValueDeserializer(workflowPropertySource)); - addDeserializer(OnEvents.ActionMode.class, - new OnEventsActionModeDeserializer(workflowPropertySource)); - addDeserializer(OperationState.ActionMode.class, - new OperationStateActionModeDeserializer(workflowPropertySource)); - addDeserializer(DefaultState.Type.class, - new DefaultStateTypeDeserializer(workflowPropertySource)); - addDeserializer(EventDefinition.Kind.class, new EventDefinitionKindDeserializer(workflowPropertySource)); - addDeserializer(ParallelState.CompletionType.class, new ParallelStateCompletionTypeDeserializer(workflowPropertySource)); - addDeserializer(Retries.class, new RetriesDeserializer(workflowPropertySource)); - addDeserializer(Functions.class, new FunctionsDeserializer(workflowPropertySource)); - addDeserializer(Events.class, new EventsDeserializer(workflowPropertySource)); - addDeserializer(Start.class, new StartDefinitionDeserializer(workflowPropertySource)); - addDeserializer(End.class, new EndDefinitionDeserializer(workflowPropertySource)); - addDeserializer(Extension.class, extensionDeserializer); - addDeserializer(FunctionDefinition.Type.class, new FunctionDefinitionTypeDeserializer(workflowPropertySource)); - addDeserializer(Transition.class, new TransitionDeserializer(workflowPropertySource)); - addDeserializer(FunctionRef.class, new FunctionRefDeserializer(workflowPropertySource)); - addDeserializer(Cron.class, new CronDeserializer(workflowPropertySource)); - addDeserializer(Schedule.class, new ScheduleDeserializer(workflowPropertySource)); - addDeserializer(DataInputSchema.class, new DataInputSchemaDeserializer(workflowPropertySource)); - } - - public ExtensionSerializer getExtensionSerializer() { - return extensionSerializer; - } - - public ExtensionDeserializer getExtensionDeserializer() { - return extensionDeserializer; - } -} \ No newline at end of file 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 cb5510c7..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/schemaclient/ResourceSchemaClient.java +++ /dev/null @@ -1,40 +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 org.everit.json.schema.loader.SchemaClient; - -import java.io.InputStream; -import java.util.Objects; - -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/CallbackStateSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/CallbackStateSerializer.java deleted file mode 100644 index 15b1f67a..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/serializers/CallbackStateSerializer.java +++ /dev/null @@ -1,53 +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.serializers; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.BeanSerializerFactory; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import com.fasterxml.jackson.databind.type.TypeFactory; -import io.serverlessworkflow.api.states.CallbackState; -import io.serverlessworkflow.api.states.DefaultState; - -import java.io.IOException; - -public class CallbackStateSerializer extends StdSerializer { - - public CallbackStateSerializer() { - this(CallbackState.class); - } - - protected CallbackStateSerializer(Class t) { - super(t); - } - - @Override - public void serialize(CallbackState callbackState, - JsonGenerator gen, - SerializerProvider provider) throws IOException { - - // set defaults for callback state - callbackState.setType(DefaultState.Type.CALLBACK); - - // serialize after setting default bean values... - BeanSerializerFactory.instance.createSerializer(provider, - TypeFactory.defaultInstance().constructType(CallbackState.class)).serialize(callbackState, - gen, - provider); - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/serializers/CronSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/CronSerializer.java deleted file mode 100644 index f080b999..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/serializers/CronSerializer.java +++ /dev/null @@ -1,61 +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.serializers; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import io.serverlessworkflow.api.cron.Cron; - -import java.io.IOException; - -public class CronSerializer extends StdSerializer { - - public CronSerializer() { - this(Cron.class); - } - - protected CronSerializer(Class t) { - super(t); - } - - @Override - public void serialize(Cron cron, - JsonGenerator gen, - SerializerProvider provider) throws IOException { - - if(cron != null) { - if((cron.getValidUntil() == null || cron.getValidUntil().isEmpty()) - && cron.getExpression() != null - && cron.getExpression().length() > 0) { - gen.writeString(cron.getExpression()); - } else { - gen.writeStartObject(); - - if(cron.getExpression() != null && cron.getExpression().length() > 0) { - gen.writeStringField("expression", cron.getExpression()); - } - - if (cron.getValidUntil() != null && cron.getValidUntil().length() > 0) { - gen.writeStringField("validUntil", cron.getValidUntil()); - } - - gen.writeEndObject(); - } - } - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/serializers/DelayStateSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/DelayStateSerializer.java deleted file mode 100644 index fd0b117c..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/serializers/DelayStateSerializer.java +++ /dev/null @@ -1,53 +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.serializers; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.BeanSerializerFactory; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import com.fasterxml.jackson.databind.type.TypeFactory; -import io.serverlessworkflow.api.states.DefaultState; -import io.serverlessworkflow.api.states.DelayState; - -import java.io.IOException; - -public class DelayStateSerializer extends StdSerializer { - - public DelayStateSerializer() { - this(DelayState.class); - } - - protected DelayStateSerializer(Class t) { - super(t); - } - - @Override - public void serialize(DelayState delayState, - JsonGenerator gen, - SerializerProvider provider) throws IOException { - - // set defaults for delay state - delayState.setType(DefaultState.Type.DELAY); - - // serialize after setting default bean values... - BeanSerializerFactory.instance.createSerializer(provider, - TypeFactory.defaultInstance().constructType(DelayState.class)).serialize(delayState, - gen, - provider); - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/serializers/EndDefinitionSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/EndDefinitionSerializer.java deleted file mode 100644 index 341f0372..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/serializers/EndDefinitionSerializer.java +++ /dev/null @@ -1,69 +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.serializers; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import io.serverlessworkflow.api.end.End; -import io.serverlessworkflow.api.produce.ProduceEvent; - -import java.io.IOException; - -public class EndDefinitionSerializer extends StdSerializer { - - public EndDefinitionSerializer() { - this(End.class); - } - - protected EndDefinitionSerializer(Class t) { - super(t); - } - - @Override - public void serialize(End end, - JsonGenerator gen, - SerializerProvider provider) throws IOException { - - if(end != null) { - if((end.getProduceEvents() == null || end.getProduceEvents().size() < 1) - && !end.isCompensate() && !end.isTerminate()) { - gen.writeBoolean(true); - } else { - gen.writeStartObject(); - - if(end.isTerminate()) { - gen.writeBooleanField("terminate", true); - } - - if (end.getProduceEvents() != null && !end.getProduceEvents().isEmpty()) { - gen.writeArrayFieldStart("produceEvents"); - for (ProduceEvent produceEvent : end.getProduceEvents()) { - gen.writeObject(produceEvent); - } - gen.writeEndArray(); - } - - if(end.isCompensate()) { - gen.writeBooleanField("compensate", true); - } - - gen.writeEndObject(); - } - } - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/serializers/EventDefinitionSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/EventDefinitionSerializer.java deleted file mode 100644 index c6fe0786..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/serializers/EventDefinitionSerializer.java +++ /dev/null @@ -1,49 +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.serializers; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.BeanSerializerFactory; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import com.fasterxml.jackson.databind.type.TypeFactory; -import io.serverlessworkflow.api.events.EventDefinition; - -import java.io.IOException; - -public class EventDefinitionSerializer extends StdSerializer { - - public EventDefinitionSerializer() { - this(EventDefinition.class); - } - - protected EventDefinitionSerializer(Class t) { - super(t); - } - - @Override - public void serialize(EventDefinition triggerEvent, - JsonGenerator gen, - SerializerProvider provider) throws IOException { - - // serialize after setting default bean values... - BeanSerializerFactory.instance.createSerializer(provider, - TypeFactory.defaultInstance().constructType(EventDefinition.class)).serialize(triggerEvent, - gen, - provider); - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/serializers/EventStateSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/EventStateSerializer.java deleted file mode 100644 index bd41a37c..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/serializers/EventStateSerializer.java +++ /dev/null @@ -1,53 +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.serializers; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.BeanSerializerFactory; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import com.fasterxml.jackson.databind.type.TypeFactory; -import io.serverlessworkflow.api.states.DefaultState; -import io.serverlessworkflow.api.states.EventState; - -import java.io.IOException; - -public class EventStateSerializer extends StdSerializer { - - public EventStateSerializer() { - this(EventState.class); - } - - protected EventStateSerializer(Class t) { - super(t); - } - - @Override - public void serialize(EventState eventState, - JsonGenerator gen, - SerializerProvider provider) throws IOException { - - // set defaults for end state - eventState.setType(DefaultState.Type.EVENT); - - // serialize after setting default bean values... - BeanSerializerFactory.instance.createSerializer(provider, - TypeFactory.defaultInstance().constructType(EventState.class)).serialize(eventState, - gen, - provider); - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/serializers/ExtensionSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/ExtensionSerializer.java deleted file mode 100644 index ecc59c48..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/serializers/ExtensionSerializer.java +++ /dev/null @@ -1,63 +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.serializers; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.BeanSerializerFactory; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import com.fasterxml.jackson.databind.type.TypeFactory; -import io.serverlessworkflow.api.interfaces.Extension; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -public class ExtensionSerializer extends StdSerializer { - - private Map> extensionsMap = new HashMap<>(); - - public ExtensionSerializer() { - this(Extension.class); - } - - protected ExtensionSerializer(Class t) { - super(t); - } - - public void addExtension(String extensionId, Class extensionClass) { - this.extensionsMap.put(extensionId, extensionClass); - } - - @Override - public void serialize(Extension extension, - JsonGenerator gen, - SerializerProvider provider) throws IOException { - - String extensionId = extension.getExtensionId(); - - if (extensionsMap.containsKey(extensionId)) { - // serialize after setting default bean values... - BeanSerializerFactory.instance.createSerializer(provider, - TypeFactory.defaultInstance().constructType(extensionsMap.get(extensionId))).serialize(extension, - gen, - provider); - } else { - throw new IllegalArgumentException("Extension handler not registered for: " + extensionId); - } - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/serializers/ForEachStateSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/ForEachStateSerializer.java deleted file mode 100644 index 2204d220..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/serializers/ForEachStateSerializer.java +++ /dev/null @@ -1,53 +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.serializers; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.BeanSerializerFactory; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import com.fasterxml.jackson.databind.type.TypeFactory; -import io.serverlessworkflow.api.states.DefaultState; -import io.serverlessworkflow.api.states.ForEachState; - -import java.io.IOException; - -public class ForEachStateSerializer extends StdSerializer { - - public ForEachStateSerializer() { - this(ForEachState.class); - } - - protected ForEachStateSerializer(Class t) { - super(t); - } - - @Override - public void serialize(ForEachState forEachState, - JsonGenerator gen, - SerializerProvider provider) throws IOException { - - // set defaults for foreach state - forEachState.setType(DefaultState.Type.FOREACH); - - // serialize after setting default bean values... - BeanSerializerFactory.instance.createSerializer(provider, - TypeFactory.defaultInstance().constructType(ForEachState.class)).serialize(forEachState, - gen, - provider); - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/serializers/FunctionRefSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/FunctionRefSerializer.java deleted file mode 100644 index 62ed982c..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/serializers/FunctionRefSerializer.java +++ /dev/null @@ -1,62 +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.serializers; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import io.serverlessworkflow.api.functions.FunctionRef; - -import java.io.IOException; - -public class FunctionRefSerializer extends StdSerializer { - - public FunctionRefSerializer() { - this(FunctionRef.class); - } - - protected FunctionRefSerializer(Class t) { - super(t); - } - - @Override - public void serialize(FunctionRef functionRef, - JsonGenerator gen, - SerializerProvider provider) throws IOException { - - if(functionRef != null) { - if((functionRef.getArguments() == null || functionRef.getArguments().isEmpty()) - && functionRef.getRefName() != null - && functionRef.getRefName().length() > 0) { - gen.writeString(functionRef.getRefName()); - } else { - gen.writeStartObject(); - - if(functionRef.getRefName() != null && functionRef.getRefName().length() > 0) { - gen.writeStringField("refName", functionRef.getRefName()); - } - - if (functionRef.getArguments() != null && !functionRef.getArguments().isEmpty()) { - gen.writeObjectField("arguments", functionRef.getArguments()); - } - - gen.writeEndObject(); - } - } - } -} - diff --git a/api/src/main/java/io/serverlessworkflow/api/serializers/InjectStateSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/InjectStateSerializer.java deleted file mode 100644 index 44f1016b..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/serializers/InjectStateSerializer.java +++ /dev/null @@ -1,53 +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.serializers; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.BeanSerializerFactory; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import com.fasterxml.jackson.databind.type.TypeFactory; -import io.serverlessworkflow.api.states.DefaultState; -import io.serverlessworkflow.api.states.InjectState; - -import java.io.IOException; - -public class InjectStateSerializer extends StdSerializer { - - public InjectStateSerializer() { - this(InjectState.class); - } - - protected InjectStateSerializer(Class t) { - super(t); - } - - @Override - public void serialize(InjectState relayState, - JsonGenerator gen, - SerializerProvider provider) throws IOException { - - // set defaults for relay state - relayState.setType(DefaultState.Type.INJECT); - - // serialize after setting default bean values... - BeanSerializerFactory.instance.createSerializer(provider, - TypeFactory.defaultInstance().constructType(InjectState.class)).serialize(relayState, - gen, - provider); - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/serializers/OperationStateSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/OperationStateSerializer.java deleted file mode 100644 index a2013be6..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/serializers/OperationStateSerializer.java +++ /dev/null @@ -1,53 +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.serializers; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.BeanSerializerFactory; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import com.fasterxml.jackson.databind.type.TypeFactory; -import io.serverlessworkflow.api.states.DefaultState; -import io.serverlessworkflow.api.states.OperationState; - -import java.io.IOException; - -public class OperationStateSerializer extends StdSerializer { - - public OperationStateSerializer() { - this(OperationState.class); - } - - protected OperationStateSerializer(Class t) { - super(t); - } - - @Override - public void serialize(OperationState operationState, - JsonGenerator gen, - SerializerProvider provider) throws IOException { - - // set defaults for delay state - operationState.setType(DefaultState.Type.OPERATION); - - // serialize after setting default bean values... - BeanSerializerFactory.instance.createSerializer(provider, - TypeFactory.defaultInstance().constructType(OperationState.class)).serialize(operationState, - gen, - provider); - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/serializers/ParallelStateSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/ParallelStateSerializer.java deleted file mode 100644 index b97b9f36..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/serializers/ParallelStateSerializer.java +++ /dev/null @@ -1,53 +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.serializers; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.BeanSerializerFactory; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import com.fasterxml.jackson.databind.type.TypeFactory; -import io.serverlessworkflow.api.states.DefaultState; -import io.serverlessworkflow.api.states.ParallelState; - -import java.io.IOException; - -public class ParallelStateSerializer extends StdSerializer { - - public ParallelStateSerializer() { - this(ParallelState.class); - } - - protected ParallelStateSerializer(Class t) { - super(t); - } - - @Override - public void serialize(ParallelState parallelState, - JsonGenerator gen, - SerializerProvider provider) throws IOException { - - // set defaults for end state - parallelState.setType(DefaultState.Type.PARALLEL); - - // serialize after setting default bean values... - BeanSerializerFactory.instance.createSerializer(provider, - TypeFactory.defaultInstance().constructType(ParallelState.class)).serialize(parallelState, - gen, - provider); - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/serializers/ScheduleSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/ScheduleSerializer.java deleted file mode 100644 index 45759238..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/serializers/ScheduleSerializer.java +++ /dev/null @@ -1,66 +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.serializers; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import io.serverlessworkflow.api.schedule.Schedule; - -import java.io.IOException; - -public class ScheduleSerializer extends StdSerializer { - - public ScheduleSerializer() { - this(Schedule.class); - } - - protected ScheduleSerializer(Class t) { - super(t); - } - - @Override - public void serialize(Schedule schedule, - JsonGenerator gen, - SerializerProvider provider) throws IOException { - - if(schedule != null) { - if(schedule.getCron() == null - && (schedule.getTimezone() == null || schedule.getTimezone().isEmpty()) - && schedule.getInterval() != null - && schedule.getInterval().length() > 0) { - gen.writeString(schedule.getInterval()); - } else { - gen.writeStartObject(); - - if(schedule.getInterval() != null && schedule.getInterval().length() > 0) { - gen.writeStringField("interval", schedule.getInterval()); - } - - if(schedule.getCron() != null) { - gen.writeObjectField("cron", schedule.getCron()); - } - - if(schedule.getTimezone() != null && schedule.getTimezone().length() > 0) { - gen.writeStringField("timezone", schedule.getTimezone()); - } - - gen.writeEndObject(); - } - } - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/serializers/StartDefinitionSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/StartDefinitionSerializer.java deleted file mode 100644 index 5c8c7997..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/serializers/StartDefinitionSerializer.java +++ /dev/null @@ -1,61 +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.serializers; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import io.serverlessworkflow.api.start.Start; - -import java.io.IOException; - -public class StartDefinitionSerializer extends StdSerializer { - - public StartDefinitionSerializer() { - this(Start.class); - } - - protected StartDefinitionSerializer(Class t) { - super(t); - } - - @Override - public void serialize(Start start, - JsonGenerator gen, - SerializerProvider provider) throws IOException { - - if(start != null) { - if(start.getStateName() != null && start.getStateName().length() > 0 - && start.getSchedule() == null) { - gen.writeString(start.getStateName()); - } else { - gen.writeStartObject(); - - if(start.getStateName() != null && start.getStateName().length() > 0) { - gen.writeStringField("stateName", start.getStateName()); - } - - if(start.getSchedule() != null) { - gen.writeObjectField("schedule", - start.getSchedule()); - } - - gen.writeEndObject(); - } - } - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/serializers/SubflowStateSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/SubflowStateSerializer.java deleted file mode 100644 index 2d569892..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/serializers/SubflowStateSerializer.java +++ /dev/null @@ -1,53 +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.serializers; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.BeanSerializerFactory; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import com.fasterxml.jackson.databind.type.TypeFactory; -import io.serverlessworkflow.api.states.DefaultState; -import io.serverlessworkflow.api.states.SubflowState; - -import java.io.IOException; - -public class SubflowStateSerializer extends StdSerializer { - - public SubflowStateSerializer() { - this(SubflowState.class); - } - - protected SubflowStateSerializer(Class t) { - super(t); - } - - @Override - public void serialize(SubflowState subflowState, - JsonGenerator gen, - SerializerProvider provider) throws IOException { - - // set defaults for end state - subflowState.setType(DefaultState.Type.SUBFLOW); - - // serialize after setting default bean values... - BeanSerializerFactory.instance.createSerializer(provider, - TypeFactory.defaultInstance().constructType(SubflowState.class)).serialize(subflowState, - gen, - provider); - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/serializers/SwitchStateSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/SwitchStateSerializer.java deleted file mode 100644 index 5dcd0d49..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/serializers/SwitchStateSerializer.java +++ /dev/null @@ -1,53 +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.serializers; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.BeanSerializerFactory; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import com.fasterxml.jackson.databind.type.TypeFactory; -import io.serverlessworkflow.api.states.DefaultState; -import io.serverlessworkflow.api.states.SwitchState; - -import java.io.IOException; - -public class SwitchStateSerializer extends StdSerializer { - - public SwitchStateSerializer() { - this(SwitchState.class); - } - - protected SwitchStateSerializer(Class t) { - super(t); - } - - @Override - public void serialize(SwitchState switchState, - JsonGenerator gen, - SerializerProvider provider) throws IOException { - - // set defaults for end state - switchState.setType(DefaultState.Type.SWITCH); - - // serialize after setting default bean values... - BeanSerializerFactory.instance.createSerializer(provider, - TypeFactory.defaultInstance().constructType(SwitchState.class)).serialize(switchState, - gen, - provider); - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/serializers/TransitionSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/TransitionSerializer.java deleted file mode 100644 index f41afb1d..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/serializers/TransitionSerializer.java +++ /dev/null @@ -1,70 +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.serializers; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.std.StdSerializer; -import io.serverlessworkflow.api.produce.ProduceEvent; -import io.serverlessworkflow.api.transitions.Transition; - -import java.io.IOException; - -public class TransitionSerializer extends StdSerializer { - - public TransitionSerializer() { - this(Transition.class); - } - - protected TransitionSerializer(Class t) { - super(t); - } - - @Override - public void serialize(Transition transition, - JsonGenerator gen, - SerializerProvider provider) throws IOException { - - if(transition != null) { - if((transition.getProduceEvents() == null || transition.getProduceEvents().size() < 1) - && !transition.isCompensate() && transition.getNextState() != null - && transition.getNextState().length() > 0) { - gen.writeString(transition.getNextState()); - } else { - gen.writeStartObject(); - - if (transition.getProduceEvents() != null && !transition.getProduceEvents().isEmpty()) { - gen.writeArrayFieldStart("produceEvents"); - for (ProduceEvent produceEvent : transition.getProduceEvents()) { - gen.writeObject(produceEvent); - } - gen.writeEndArray(); - } - - if(transition.isCompensate()) { - gen.writeBooleanField("compensate", true); - } - - if(transition.getNextState() != null && transition.getNextState().length() > 0) { - gen.writeStringField("nextState", transition.getNextState()); - } - - gen.writeEndObject(); - } - } - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/serializers/WorkflowSerializer.java b/api/src/main/java/io/serverlessworkflow/api/serializers/WorkflowSerializer.java deleted file mode 100644 index bed88d36..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/serializers/WorkflowSerializer.java +++ /dev/null @@ -1,191 +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.serializers; - -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.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.getSchemaVersion() != null && !workflow.getSchemaVersion().isEmpty()) { - gen.writeStringField("schemaVersion", - workflow.getSchemaVersion()); - } - - if (workflow.getExtensions() != null && !workflow.getExpressionLang().isEmpty()) { - gen.writeStringField("expressionLang", - workflow.getExpressionLang()); - } - - if (workflow.getExecTimeout() != null) { - gen.writeObjectField("execTimeout", workflow.getExecTimeout()); - } - - if (workflow.isKeepActive()) { - gen.writeBooleanField("keepActive", workflow.isKeepActive()); - } - - 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.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]; - } - return new String(hexChars); - } -} \ No newline at end of file diff --git a/api/src/main/java/io/serverlessworkflow/api/utils/Utils.java b/api/src/main/java/io/serverlessworkflow/api/utils/Utils.java deleted file mode 100644 index cb304789..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/utils/Utils.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.utils; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.stream.Collectors; - -public class Utils { - - @SuppressWarnings("DefaultCharset") - public static String getResourceFileAsString(String fileName) throws IOException { - ClassLoader classLoader = ClassLoader.getSystemClassLoader(); - try (InputStream is = classLoader.getResourceAsStream(fileName)) { - if (is == null) return null; - try (InputStreamReader isr = new InputStreamReader(is); - BufferedReader reader = new BufferedReader(isr)) { - return reader.lines().collect(Collectors.joining(System.lineSeparator())); - } - } - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/validation/ValidationError.java b/api/src/main/java/io/serverlessworkflow/api/validation/ValidationError.java deleted file mode 100644 index 363310e5..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/validation/ValidationError.java +++ /dev/null @@ -1,49 +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.validation; - -public class ValidationError { - private static final String MSG_FORMAT = "%s:%s"; - public static final String SCHEMA_VALIDATION = "schemavalidation"; - public static final String WORKFLOW_VALIDATION = "workflowvalidation"; - - private String message; - private String type; - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - @Override - public String toString() { - return String.format(MSG_FORMAT, - type, - message); - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/validation/WorkflowSchemaLoader.java b/api/src/main/java/io/serverlessworkflow/api/validation/WorkflowSchemaLoader.java deleted file mode 100644 index 1a35a45e..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/validation/WorkflowSchemaLoader.java +++ /dev/null @@ -1,39 +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.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; - -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(); - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/workflow/BaseWorkflow.java b/api/src/main/java/io/serverlessworkflow/api/workflow/BaseWorkflow.java deleted file mode 100644 index fb4a094f..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/workflow/BaseWorkflow.java +++ /dev/null @@ -1,78 +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.workflow; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; -import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; -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 - */ -public class BaseWorkflow { - - private static JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(); - private static YamlObjectMapper yamlObjectMapper = new YamlObjectMapper(); - - 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 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 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; - } - } -} diff --git a/api/src/main/java/io/serverlessworkflow/api/workflow/Events.java b/api/src/main/java/io/serverlessworkflow/api/workflow/Events.java deleted file mode 100644 index 2e0443f9..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/workflow/Events.java +++ /dev/null @@ -1,52 +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.workflow; - -import io.serverlessworkflow.api.events.EventDefinition; - -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; - } - - 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 deleted file mode 100644 index 928a7af5..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/workflow/Functions.java +++ /dev/null @@ -1,52 +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.workflow; - -import io.serverlessworkflow.api.functions.FunctionDefinition; - -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; - } - - 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 deleted file mode 100644 index 108c4233..00000000 --- a/api/src/main/java/io/serverlessworkflow/api/workflow/Retries.java +++ /dev/null @@ -1,52 +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.workflow; - -import io.serverlessworkflow.api.retry.RetryDefinition; - -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; - } - - public void setRetryDefs(List retryDefs) { - this.retryDefs = retryDefs; - } -} diff --git a/api/src/main/java/io/serverlessworkflow/serialization/BeanDeserializerModifierWithValidation.java b/api/src/main/java/io/serverlessworkflow/serialization/BeanDeserializerModifierWithValidation.java new file mode 100644 index 00000000..e4e019ac --- /dev/null +++ b/api/src/main/java/io/serverlessworkflow/serialization/BeanDeserializerModifierWithValidation.java @@ -0,0 +1,35 @@ +/* + * 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.serialization; + +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.DeserializationConfig; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.deser.BeanDeserializer; +import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier; + +public class BeanDeserializerModifierWithValidation extends BeanDeserializerModifier { + + private static final long serialVersionUID = 1L; + + @Override + public JsonDeserializer modifyDeserializer( + DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer deserializer) { + return deserializer instanceof BeanDeserializer + ? new BeanDeserializerWithValidation((BeanDeserializer) deserializer) + : deserializer; + } +} diff --git a/api/src/main/java/io/serverlessworkflow/serialization/BeanDeserializerWithValidation.java b/api/src/main/java/io/serverlessworkflow/serialization/BeanDeserializerWithValidation.java new file mode 100644 index 00000000..a77e117d --- /dev/null +++ b/api/src/main/java/io/serverlessworkflow/serialization/BeanDeserializerWithValidation.java @@ -0,0 +1,51 @@ +/* + * 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.serialization; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.BeanDeserializer; +import com.fasterxml.jackson.databind.deser.BeanDeserializerBase; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import java.io.IOException; +import java.util.Set; + +public class BeanDeserializerWithValidation extends BeanDeserializer { + private static final long serialVersionUID = 1L; + private static final Validator validator = + Validation.buildDefaultValidatorFactory().getValidator(); + + protected BeanDeserializerWithValidation(BeanDeserializerBase src) { + super(src); + } + + private void validate(T t) throws IOException { + Set> violations = validator.validate(t); + if (!violations.isEmpty()) { + throw new ConstraintViolationException(violations); + } + } + + @Override + public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + Object instance = super.deserialize(p, ctxt); + validate(instance); + return instance; + } +} diff --git a/api/src/main/java/io/serverlessworkflow/serialization/URIDeserializer.java b/api/src/main/java/io/serverlessworkflow/serialization/URIDeserializer.java new file mode 100644 index 00000000..a3269ab6 --- /dev/null +++ b/api/src/main/java/io/serverlessworkflow/serialization/URIDeserializer.java @@ -0,0 +1,39 @@ +/* + * 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.serialization; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +public class URIDeserializer extends JsonDeserializer { + @Override + public URI deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + try { + String uriStr = p.getValueAsString(); + if (uriStr == null) { + throw new JsonMappingException(p, "URI is not an string"); + } + return new URI(uriStr); + } catch (URISyntaxException ex) { + throw new JsonMappingException(p, ex.getMessage()); + } + } +} diff --git a/api/src/main/java/io/serverlessworkflow/serialization/URISerializer.java b/api/src/main/java/io/serverlessworkflow/serialization/URISerializer.java new file mode 100644 index 00000000..a36561d2 --- /dev/null +++ b/api/src/main/java/io/serverlessworkflow/serialization/URISerializer.java @@ -0,0 +1,31 @@ +/* + * 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.serialization; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import java.io.IOException; +import java.net.URI; + +public class URISerializer extends JsonSerializer { + + @Override + public void serialize(URI value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + gen.writeString(value.toString()); + } +} diff --git a/api/src/main/resources/schema/actions/action.json b/api/src/main/resources/schema/actions/action.json deleted file mode 100644 index 1a65e36b..00000000 --- a/api/src/main/resources/schema/actions/action.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.actions.Action", - "description": "Action Definition", - "properties": { - "name": { - "type": "string", - "description": "Unique action definition name" - }, - "functionRef": { - "description": "References a reusable function definition to be invoked", - "$ref": "../functions/functionref.json" - }, - "eventRef": { - "description": "References a 'trigger' and 'result' reusable event definitions", - "$ref": "../events/eventref.json" - }, - "timeout": { - "type": "string", - "description": "Time period to wait for function execution to complete" - }, - "actionDataFilter": { - "$ref": "../filters/actiondatafilter.json" - } - }, - "oneOf": [ - { - "required": [ - "functionRef" - ] - }, - { - "required": [ - "eventRef" - ] - } - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/branches/branch.json b/api/src/main/resources/schema/branches/branch.json deleted file mode 100644 index 53094bfe..00000000 --- a/api/src/main/resources/schema/branches/branch.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.branches.Branch", - "description": "Branch Definition", - "properties": { - "name": { - "type": "string", - "description": "Branch name" - }, - "actions": { - "type": "array", - "description": "Actions to be executed in this branch", - "items": { - "type": "object", - "$ref": "../actions/action.json" - } - }, - "workflowId": { - "type": "string", - "description": "Unique Id of a workflow to be executed in this branch" - } - }, - "oneOf": [ - { - "required": [ - "name", - "actions" - ] - }, - { - "required": [ - "name", - "workflowId " - ] - } - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/correlation/correlationdef.json b/api/src/main/resources/schema/correlation/correlationdef.json deleted file mode 100644 index 7f271b08..00000000 --- a/api/src/main/resources/schema/correlation/correlationdef.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.correlation.CorrelationDef", - "description": "CloudEvent correlation definition", - "properties": { - "contextAttributeName": { - "type": "string", - "description": "CloudEvent Extension Context Attribute name", - "minLength": 1 - }, - "contextAttributeValue": { - "type": "string", - "description": "CloudEvent Extension Context Attribute value", - "minLength": 1 - } - }, - "required": [ - "contextAttributeName" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/cron/crondef.json b/api/src/main/resources/schema/cron/crondef.json deleted file mode 100644 index 67bb43c5..00000000 --- a/api/src/main/resources/schema/cron/crondef.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.cron.Cron", - "description": "Schedule cron definition", - "properties": { - "expression": { - "type": "string", - "description": "Repeating interval (cron expression) describing when the workflow instance should be created" - }, - "validUntil": { - "type": "string", - "description": "Specific date and time (ISO 8601 format) when the cron expression invocation is no longer valid" - } - }, - "required": [ - "expression" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/datainputschema/datainputschema.json b/api/src/main/resources/schema/datainputschema/datainputschema.json deleted file mode 100644 index 01853334..00000000 --- a/api/src/main/resources/schema/datainputschema/datainputschema.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.datainputschema.DataInputSchema", - "description": "Workflow data input schema", - "properties": { - "schema": { - "type": "string", - "description": "URI of the JSON Schema used to validate the workflow data input", - "minLength": 1 - }, - "failOnValidationErrors": { - "type": "boolean", - "default": true, - "description": "Determines if workfow execution should continue if there are validation errors" - } - }, - "required": [ - "schema", - "failOnValidationErrors" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/default/defaultdef.json b/api/src/main/resources/schema/default/defaultdef.json deleted file mode 100644 index ad406785..00000000 --- a/api/src/main/resources/schema/default/defaultdef.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.defaultdef.DefaultDefinition", - "description": "Switch state default definition", - "properties": { - "transition": { - "$ref": "../transitions/transition.json", - "description": "Next transition of the workflow if there is valid matches" - }, - "end": { - "$ref": "../end/end.json", - "description": "Workflow end definition" - } - }, - "oneOf": [ - { - "required": [ - "transition" - ] - }, - { - "required": [ - "end" - ] - } - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/end/end.json b/api/src/main/resources/schema/end/end.json deleted file mode 100644 index 7b959a2a..00000000 --- a/api/src/main/resources/schema/end/end.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.end.End", - "description": "State end definition", - "properties": { - "terminate": { - "type": "boolean", - "default": false, - "description": "If true, completes all execution flows in the given workflow instance" - }, - "produceEvents": { - "type": "array", - "description": "Array of events to be produced", - "items": { - "type": "object", - "$ref": "../produce/produceevent.json" - } - }, - "compensate": { - "type": "boolean", - "default": false, - "description": "If set to true, triggers workflow compensation when before workflow executin completes. Default is false" - } - }, - "required": [ - "kind" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/error/error.json b/api/src/main/resources/schema/error/error.json deleted file mode 100644 index c3996430..00000000 --- a/api/src/main/resources/schema/error/error.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.error.Error", - "properties": { - "error": { - "type": "string", - "description": "Domain-specific error name, or '*' to indicate all possible errors", - "minLength": 1 - }, - "code": { - "type": "string", - "description": "Error code. Can be used in addition to the name to help runtimes resolve to technical errors/exceptions. Should not be defined if error is set to '*'", - "minLength": 1 - }, - "retryRef": { - "type": "string", - "description": "References a unique name of a retry definition.", - "minLength": 1 - }, - "transition": { - "$ref": "../transitions/transition.json", - "description": "Transition to next state to handle the error. If retryRef is defined, this transition is taken only if retries were unsuccessful." - }, - "end": { - "description": "End workflow execution in case of this error. If retryRef is defined, this ends workflow only if retries were unsuccessful.", - "$ref": "../end/end.json" - } - }, - "required": [ - "error", - "transition" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/events/eventdef.json b/api/src/main/resources/schema/events/eventdef.json deleted file mode 100644 index 6670b856..00000000 --- a/api/src/main/resources/schema/events/eventdef.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.events.EventDefinition", - "properties": { - "name": { - "type": "string", - "description": "Event Definition unique name", - "minLength": 1 - }, - "source": { - "type": "string", - "description": "CloudEvent source UUID" - }, - "type": { - "type": "string", - "description": "CloudEvent type" - }, - "correlation": { - "type": "array", - "description": "CloudEvent correlation definitions", - "minItems": 1, - "items": { - "type": "object", - "$ref": "../correlation/correlationdef.json" - } - }, - "kind": { - "type" : "string", - "enum": ["consumed", "produced"], - "description": "Defines the events as either being consumed or produced by the workflow. Default is consumed", - "default": "consumed" - }, - "metadata": { - "$ref": "../metadata/metadata.json" - } - }, - "if": { - "properties": { - "kind": { - "const": "consumed" - } - } - }, - "then": { - "required": [ - "name", - "source", - "type" - ] - }, - "else": { - "required": [ - "name", - "type" - ] - } -} \ No newline at end of file diff --git a/api/src/main/resources/schema/events/eventref.json b/api/src/main/resources/schema/events/eventref.json deleted file mode 100644 index a113cf49..00000000 --- a/api/src/main/resources/schema/events/eventref.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.events.EventRef", - "description": "Event References", - "properties": { - "triggerEventRef": { - "type": "string", - "description": "Reference to the unique name of a 'produced' event definition" - }, - "resultEventRef": { - "type": "string", - "description": "Reference to the unique name of a 'consumed' event definition" - }, - "data": { - "type": "string", - "description": "Expression which selects parts of the states data output to become the data of the produced event." - }, - "contextAttributes": { - "existingJavaType": "java.util.Map", - "type": "object", - "description": "Add additional extension context attributes to the produced event" - } - }, - "required": [ - "triggerEventRef", - "resultEventRef" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/events/onevents.json b/api/src/main/resources/schema/events/onevents.json deleted file mode 100644 index 2d4ed621..00000000 --- a/api/src/main/resources/schema/events/onevents.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.events.OnEvents", - "description": "Actions to be performed on Events arrival", - "properties": { - "eventRefs": { - "type": "array", - "description": "References one or more unique event names in the defined workflow events", - "items": { - "type": "object", - "existingJavaType": "java.lang.String" - } - }, - "actionMode": { - "type": "string", - "enum": [ - "sequential", - "parallel" - ], - "description": "Specifies how actions are to be performed (in sequence of parallel)", - "default": "sequential" - }, - "actions": { - "type": "array", - "description": "Actions to be performed.", - "items": { - "type": "object", - "$ref": "../actions/action.json" - } - }, - "eventDataFilter": { - "$ref": "../filters/eventdatafilter.json" - } - }, - "required": [ - "eventRefs", - "actions" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/exectimeout/exectimeout.json b/api/src/main/resources/schema/exectimeout/exectimeout.json deleted file mode 100644 index 6db50989..00000000 --- a/api/src/main/resources/schema/exectimeout/exectimeout.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.exectimeout.ExecTimeout", - "properties": { - "duration": { - "type": "string", - "description": "Timeout duration (ISO 8601 duration format)", - "minLength": 1 - }, - "interrupt": { - "type": "boolean", - "description": "If `false`, workflow instance is allowed to finish current execution. If `true`, current workflow execution is abrupted.", - "default": false - }, - "runBefore": { - "type": "string", - "description": "Name of a workflow state to be executed before workflow instance is terminated", - "minLength": 1 - } - }, - "required": [ - "duration" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/filters/actiondatafilter.json b/api/src/main/resources/schema/filters/actiondatafilter.json deleted file mode 100644 index 06edc421..00000000 --- a/api/src/main/resources/schema/filters/actiondatafilter.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.filters.ActionDataFilter", - "properties": { - "fromStateData": { - "type": "string", - "description": "Workflow expression that selects state data that the state action can use" - }, - "results": { - "type": "string", - "description": "Workflow expression that filters the actions data results" - }, - "toStateData": { - "type": "string", - "description": "Workflow expression that selects a state data element to which the action results should be added/merged into. If not specified, denote, the top-level state data element" - } - }, - "required": [] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/filters/eventdatafilter.json b/api/src/main/resources/schema/filters/eventdatafilter.json deleted file mode 100644 index 03614471..00000000 --- a/api/src/main/resources/schema/filters/eventdatafilter.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.filters.EventDataFilter", - "properties": { - "data": { - "type": "string", - "description": "Workflow expression that filters of the event data (payload)" - }, - "toStateData": { - "type": "string", - "description": " Workflow expression that selects a state data element to which the event payload should be added/merged into. If not specified, denotes, the top-level state data element." - } - }, - "required": [] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/filters/statedatafilter.json b/api/src/main/resources/schema/filters/statedatafilter.json deleted file mode 100644 index 0859d2d1..00000000 --- a/api/src/main/resources/schema/filters/statedatafilter.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.filters.StateDataFilter", - "properties": { - "input": { - "type": "string", - "description": "Workflow expression to filter the state data input" - }, - "output": { - "type": "string", - "description": "Workflow expression that filters the state data output" - } - }, - "required": [] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/functions/functiondef.json b/api/src/main/resources/schema/functions/functiondef.json deleted file mode 100644 index ded52196..00000000 --- a/api/src/main/resources/schema/functions/functiondef.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.functions.FunctionDefinition", - "properties": { - "name": { - "type": "string", - "description": "Function unique name", - "minLength": 1 - }, - "operation": { - "type": "string", - "description": "If type is `rest`, #. If type is `rpc`, ##. If type is `expression`, defines the workflow expression.", - "minLength": 1 - }, - "type": { - "type": "string", - "description": "Defines the function type. Is either `rest`, `rpc` or `expression`. Default is `rest`", - "enum": [ - "rest", - "rpc", - "expression" - ], - "default": "rest" - }, - "metadata": { - "$ref": "../metadata/metadata.json" - } - }, - "required": [ - "name" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/functions/functionref.json b/api/src/main/resources/schema/functions/functionref.json deleted file mode 100644 index f8c25b15..00000000 --- a/api/src/main/resources/schema/functions/functionref.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.functions.FunctionRef", - "properties": { - "refName": { - "type": "string", - "description": "Name of the referenced function", - "minLength": 1 - }, - "arguments": { - "type": "object", - "description": "Function arguments", - "existingJavaType": "com.fasterxml.jackson.databind.JsonNode" - } - }, - "required": [ - "refName" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/metadata/metadata.json b/api/src/main/resources/schema/metadata/metadata.json deleted file mode 100644 index c56687a5..00000000 --- a/api/src/main/resources/schema/metadata/metadata.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "object", - "description": "Metadata", - "existingJavaType": "java.util.Map" -} \ No newline at end of file diff --git a/api/src/main/resources/schema/produce/produceevent.json b/api/src/main/resources/schema/produce/produceevent.json deleted file mode 100644 index b5bb7d5f..00000000 --- a/api/src/main/resources/schema/produce/produceevent.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.produce.ProduceEvent", - "properties": { - "eventRef": { - "type": "string", - "description": "References a name of a defined event", - "minLength": 1 - }, - "data": { - "type": "string", - "description": "Workflow expression which selects parts of the states data output to become the data of the produced event" - } - }, - "required": [ - "eventRef" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/repeat/repeat.json b/api/src/main/resources/schema/repeat/repeat.json deleted file mode 100644 index 826b2787..00000000 --- a/api/src/main/resources/schema/repeat/repeat.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.repeat.Repeat", - "properties": { - "expression": { - "type": "string", - "description": "Expression evaluated against SubFlow state data. SubFlow will repeat execution as long as this expression is true or until the max property count is reached", - "minLength": 1 - }, - "checkBefore": { - "type": "boolean", - "description": "If true, the expression is evaluated before each repeat execution, if false the expression is evaluated after each repeat execution", - "default": true - }, - "max": { - "type": "integer", - "description": "Sets the maximum amount of repeat executions", - "minimum": 0 - }, - "continueOnError": { - "type": "boolean", - "description": "If true, repeats executions in a case unhandled errors propagate from the sub-workflow to this state", - "default": false - }, - "stopOnEvents": { - "type" : "array", - "description": "List referencing defined consumed workflow events. SubFlow will repeat execution until one of the defined events is consumed, or until the max property count is reached", - "items": { - "type": "object", - "existingJavaType": "java.lang.String" - } - } - }, - "required": [ - "nextState" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/retry/retrydef.json b/api/src/main/resources/schema/retry/retrydef.json deleted file mode 100644 index 9d6ea91c..00000000 --- a/api/src/main/resources/schema/retry/retrydef.json +++ /dev/null @@ -1,43 +0,0 @@ - { - "type": "object", - "javaType": "io.serverlessworkflow.api.retry.RetryDefinition", - "description": "Retry Definition", - "properties": { - "name": { - "type": "string", - "description": "Unique retry strategy name", - "minLength": 1 - }, - "delay": { - "type": "string", - "description": "Time delay between retry attempts (ISO 8601 duration format)" - }, - "maxDelay": { - "type": "string", - "description": "Maximum time delay between retry attempts (ISO 8601 duration format)" - }, - "increment": { - "type": "string", - "description": "Static value by which the delay increases during each attempt (ISO 8601 time format)" - }, - "multiplier": { - "type": "string", - "description": "Multiplier value by which interval increases during each attempt (ISO 8601 time format)" - }, - "maxAttempts": { - "type": "string", - "default": "0", - "description": "Maximum number of retry attempts. Value of 0 means no retries are performed" - }, - "jitter": { - "type": "string", - "minimum": 0.0, - "maximum": 1.0, - "description": "Absolute maximum amount of random time added or subtracted from the delay between each retry (ISO 8601 duration format)" - } - }, - "required": [ - "name", - "maxAttempts" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/schedule/schedule.json b/api/src/main/resources/schema/schedule/schedule.json deleted file mode 100644 index c384540f..00000000 --- a/api/src/main/resources/schema/schedule/schedule.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.schedule.Schedule", - "description": "Start state schedule definition", - "properties": { - "interval": { - "type": "string", - "description": "Time interval (ISO 8601 format) describing when the workflow starting state is active" - }, - "cron": { - "description": "Schedule cron definition", - "$ref": "../cron/crondef.json" - }, - "timezone": { - "type": "string", - "description": "Timezone name used to evaluate the cron expression. Not used for interval as timezone can be specified there directly. If not specified, should default to local machine timezone." - } - }, - "oneOf": [ - { - "required": [ - "interval" - ] - }, - { - "required": [ - "cron" - ] - } - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/start/start.json b/api/src/main/resources/schema/start/start.json deleted file mode 100644 index 3f1e10ee..00000000 --- a/api/src/main/resources/schema/start/start.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.start.Start", - "description": "State start definition", - "properties": { - "stateName": { - "type": "string", - "description": "Name of the starting workflow state", - "minLength": 1 - }, - "schedule": { - "description": "Define when the time/repeating intervals at which workflow instances can/should be started", - "$ref": "../schedule/schedule.json" - } - }, - "required": [ - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/states/callbackstate.json b/api/src/main/resources/schema/states/callbackstate.json deleted file mode 100644 index 3cef4586..00000000 --- a/api/src/main/resources/schema/states/callbackstate.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.states.CallbackState", - "javaInterfaces": [ - "io.serverlessworkflow.api.interfaces.State" - ], - "description": "This state is used to wait for events from event sources and then transitioning to a next state", - "extends": { - "$ref": "defaultstate.json" - }, - "properties": { - "action": { - "description": "Defines the action to be executed", - "$ref": "../actions/action.json" - }, - "eventRef": { - "type" : "string", - "description": "References an unique callback event name in the defined workflow events" - }, - "timeout": { - "type": "string", - "description": "Time period to wait for incoming events (ISO 8601 format)" - }, - "eventDataFilter": { - "description": "Callback event data filter definition", - "$ref": "../filters/eventdatafilter.json" - }, - "usedForCompensation": { - "type": "boolean", - "default": false, - "description": "If true, this state is used to compensate another state. Default is false" - } - }, - "required": [ - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/states/defaultstate.json b/api/src/main/resources/schema/states/defaultstate.json deleted file mode 100644 index 45a5afac..00000000 --- a/api/src/main/resources/schema/states/defaultstate.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.states.DefaultState", - "javaInterfaces": [ - "io.serverlessworkflow.api.interfaces.State" - ], - "description": "Default State", - "properties": { - "id": { - "type": "string", - "description": "State unique identifier", - "minLength": 1 - }, - "name": { - "type": "string", - "description": "Unique name of the state", - "minLength": 1 - }, - "type": { - "type": "string", - "enum": [ - "event", - "operation", - "switch", - "delay", - "parallel", - "subflow", - "inject", - "foreach", - "callback" - ], - "description": "State type" - }, - "end": { - "$ref": "../end/end.json", - "description": "Defines this states end" - }, - "stateDataFilter": { - "$ref": "../filters/statedatafilter.json", - "description": "State data filter definition" - }, - "metadata": { - "$ref": "../metadata/metadata.json" - }, - "transition": { - "$ref": "../transitions/transition.json" - }, - "onErrors": { - "type": "array", - "description": "State error handling definitions", - "items": { - "type": "object", - "$ref": "../error/error.json" - } - }, - "compensatedBy": { - "type": "string", - "minLength": 1, - "description": "Unique Name of a workflow state which is responsible for compensation of this state" - } - }, - "required": [ - "name", - "type" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/states/delaystate.json b/api/src/main/resources/schema/states/delaystate.json deleted file mode 100644 index 3d0eab4c..00000000 --- a/api/src/main/resources/schema/states/delaystate.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.states.DelayState", - "javaInterfaces": [ - "io.serverlessworkflow.api.interfaces.State" - ], - "description": "This state is used to wait for events from event sources and then transitioning to a next state", - "extends": { - "$ref": "defaultstate.json" - }, - "properties": { - "timeDelay": { - "type": "string", - "description": "Amount of time (ISO 8601 format) to delay" - }, - "usedForCompensation": { - "type": "boolean", - "default": false, - "description": "If true, this state is used to compensate another state. Default is false" - } - }, - "required": [ - "timeDelay" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/states/eventstate.json b/api/src/main/resources/schema/states/eventstate.json deleted file mode 100644 index f6278968..00000000 --- a/api/src/main/resources/schema/states/eventstate.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.states.EventState", - "javaInterfaces": [ - "io.serverlessworkflow.api.interfaces.State" - ], - "description": "This state is used to wait for events from event sources and then to invoke one or more functions to run in sequence or in parallel.", - "extends": { - "$ref": "defaultstate.json" - }, - "properties": { - "exclusive": { - "type": "boolean", - "default": true, - "description": "If true consuming one of the defined events causes its associated actions to be performed. If false all of the defined events must be consumed in order for actions to be performed" - }, - "onEvents": { - "type": "array", - "description": "Define what events trigger one or more actions to be performed", - "items": { - "type": "object", - "$ref": "../events/onevents.json" - } - }, - "timeout": { - "type": "string", - "description": "Time period to wait for incoming events (ISO 8601 format)" - } - }, - "required": [ - "onevents" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/states/foreachstate.json b/api/src/main/resources/schema/states/foreachstate.json deleted file mode 100644 index b25396da..00000000 --- a/api/src/main/resources/schema/states/foreachstate.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.states.ForEachState", - "javaInterfaces": [ - "io.serverlessworkflow.api.interfaces.State" - ], - "description": "Execute a set of defined actions or workflows for each element of a data array", - "extends": { - "$ref": "defaultstate.json" - }, - "properties": { - "inputCollection": { - "type": "string", - "description": "Workflow expression selecting an array element of the states data" - }, - "outputCollection": { - "type": "string", - "description": "Workflow expression specifying an array element of the states data to add the results of each iteration" - }, - "iterationParam": { - "type": "string", - "description": "Name of the iteration parameter that can be referenced in actions/workflow. For each parallel iteration, this param should contain an unique element of the inputCollection array" - }, - "max": { - "type": "integer", - "default": "0", - "minimum": 0, - "description": "Specifies how upper bound on how many iterations may run in parallel" - }, - "actions": { - "type": "array", - "description": "Actions to be executed for each of the elements of inputCollection", - "items": { - "type": "object", - "$ref": "../actions/action.json" - } - }, - "workflowId": { - "type": "string", - "description": "Unique Id of a workflow to be executed for each of the elements of inputCollection" - }, - "usedForCompensation": { - "type": "boolean", - "default": false, - "description": "If true, this state is used to compensate another state. Default is false" - } - }, - "oneOf": [ - { - "required": [ - "name", - "type", - "inputCollection", - "inputParameter", - "workflowId", - "end" - ] - }, - { - "required": [ - "name", - "type", - "inputCollection", - "inputParameter", - "workflowId", - "transition" - ] - }, - { - "required": [ - "start", - "name", - "type", - "inputCollection", - "inputParameter", - "workflowId", - "end" - ] - }, - { - "required": [ - "start", - "name", - "type", - "inputCollection", - "inputParameter", - "workflowId", - "transition" - ] - }, - { - "required": [ - "name", - "type", - "inputCollection", - "inputParameter", - "actions", - "end" - ] - }, - { - "required": [ - "name", - "type", - "inputCollection", - "inputParameter", - "actions", - "transition" - ] - }, - { - "required": [ - "start", - "name", - "type", - "inputCollection", - "inputParameter", - "actions", - "end" - ] - }, - { - "required": [ - "start", - "name", - "type", - "inputCollection", - "inputParameter", - "actions", - "transition" - ] - } - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/states/injectstate.json b/api/src/main/resources/schema/states/injectstate.json deleted file mode 100644 index d0d10589..00000000 --- a/api/src/main/resources/schema/states/injectstate.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.states.InjectState", - "javaInterfaces": [ - "io.serverlessworkflow.api.interfaces.State" - ], - "description": "Set up and inject the state's data input to data output. Does not perform any actions", - "extends": { - "$ref": "defaultstate.json" - }, - "properties": { - "data": { - "type": "object", - "description": "JSON object which can be set as states data input and can be manipulated via filters", - "existingJavaType": "com.fasterxml.jackson.databind.JsonNode" - }, - "usedForCompensation": { - "type": "boolean", - "default": false, - "description": "If true, this state is used to compensate another state. Default is false" - } - }, - "required": [ - "inject" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/states/operationstate.json b/api/src/main/resources/schema/states/operationstate.json deleted file mode 100644 index 8d8211a9..00000000 --- a/api/src/main/resources/schema/states/operationstate.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.states.OperationState", - "javaInterfaces": [ - "io.serverlessworkflow.api.interfaces.State" - ], - "description": "This state allows one or more functions to run in sequence or in parallel without waiting for any event.", - "extends": { - "$ref": "defaultstate.json" - }, - "properties": { - "actionMode": { - "type": "string", - "enum": [ - "sequential", - "parallel" - ], - "description": "Specifies whether functions are executed in sequence or in parallel." - }, - "actions": { - "type": "array", - "description": "Actions Definitions", - "items": { - "type": "object", - "$ref": "../actions/action.json" - } - }, - "usedForCompensation": { - "type": "boolean", - "default": false, - "description": "If true, this state is used to compensate another state. Default is false" - } - }, - "required": [ - "name", - "actionMode", - "actions" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/states/parallelstate.json b/api/src/main/resources/schema/states/parallelstate.json deleted file mode 100644 index 824f3ff5..00000000 --- a/api/src/main/resources/schema/states/parallelstate.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.states.ParallelState", - "javaInterfaces": [ - "io.serverlessworkflow.api.interfaces.State" - ], - "description": "Consists of a number of states that are executed in parallel", - "extends": { - "$ref": "defaultstate.json" - }, - "properties": { - "branches": { - "type": "array", - "description": "Branch Definitions", - "items": { - "type": "object", - "$ref": "../branches/branch.json" - } - }, - "completionType": { - "type" : "string", - "enum": ["and", "xor", "n_of_m"], - "description": "Option types on how to complete branch execution.", - "default": "and" - }, - "n": { - "type": "string", - "default": "0", - "description": "Used when completionType is set to 'n_of_m' to specify the 'N' value" - }, - "usedForCompensation": { - "type": "boolean", - "default": false, - "description": "If true, this state is used to compensate another state. Default is false" - } - }, - "required": [ - "branches" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/states/subflowstate.json b/api/src/main/resources/schema/states/subflowstate.json deleted file mode 100644 index 618ab8a9..00000000 --- a/api/src/main/resources/schema/states/subflowstate.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.states.SubflowState", - "javaInterfaces": [ - "io.serverlessworkflow.api.interfaces.State" - ], - "description": "Defines a sub-workflow to be executed", - "extends": { - "$ref": "defaultstate.json" - }, - "properties": { - "waitForCompletion": { - "type": "boolean", - "default": false, - "description": "Workflow execution must wait for local workflow to finish before continuing." - }, - "workflowId": { - "type": "string", - "description": "Sub-workflow unique id." - }, - "usedForCompensation": { - "type": "boolean", - "default": false, - "description": "If true, this state is used to compensate another state. Default is false" - }, - "repeat": { - "$ref": "../repeat/repeat.json", - "description": "SubFlow state repeat exec definition" - } - }, - "required": [ - "workflowId" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/states/switchstate.json b/api/src/main/resources/schema/states/switchstate.json deleted file mode 100644 index e421397d..00000000 --- a/api/src/main/resources/schema/states/switchstate.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.states.SwitchState", - "javaInterfaces": [ - "io.serverlessworkflow.api.interfaces.State" - ], - "description": "Permits transitions to other states based on criteria matching", - "extends": { - "$ref": "defaultstate.json" - }, - "properties": { - "eventConditions": { - "type": "array", - "description": "Defines conditions evaluated against events", - "items": { - "type": "object", - "$ref": "../switchconditions/eventcondition.json" - } - }, - "dataConditions": { - "type": "array", - "description": "Defines conditions evaluated against state data", - "items": { - "type": "object", - "$ref": "../switchconditions/datacondition.json" - } - }, - "eventTimeout": { - "type": "string", - "description": "If eventConditions is used, defines the time period to wait for events (ISO 8601 format)" - }, - "default": { - "description": "Default transition of the workflow if there is no matching data conditions. Can include a transition or end definition", - "$ref": "../default/defaultdef.json" - }, - "usedForCompensation": { - "type": "boolean", - "default": false, - "description": "If true, this state is used to compensate another state. Default is false" - } - }, - "required": [ - "default" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/switchconditions/datacondition.json b/api/src/main/resources/schema/switchconditions/datacondition.json deleted file mode 100644 index 468065f0..00000000 --- a/api/src/main/resources/schema/switchconditions/datacondition.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.switchconditions.DataCondition", - "javaInterfaces": [ - "io.serverlessworkflow.api.interfaces.SwitchCondition" - ], - "description": "Switch state data based condition", - "properties": { - "name": { - "type": "string", - "description": "Data condition name" - }, - "condition": { - "type": "string", - "description": "Workflow expression evaluated against state data. True if results are not empty" - }, - "transition": { - "$ref": "../transitions/transition.json", - "description": "Next transition of the workflow if there is valid matches" - }, - "end": { - "$ref": "../end/end.json", - "description": "Workflow end definition" - } - }, - "required": [ - "condition", - "transition" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/switchconditions/eventcondition.json b/api/src/main/resources/schema/switchconditions/eventcondition.json deleted file mode 100644 index 887a96f7..00000000 --- a/api/src/main/resources/schema/switchconditions/eventcondition.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.switchconditions.EventCondition", - "javaInterfaces": ["io.serverlessworkflow.api.interfaces.SwitchCondition"], - "description": "Switch state data event condition", - "properties": { - "name": { - "type": "string", - "description": "Event condition name" - }, - "eventRef": { - "type" : "string", - "description": "References an unique event name in the defined workflow events" - }, - "eventDataFilter": { - "description": "Callback event data filter definition", - "$ref": "../filters/eventdatafilter.json" - }, - "transition": { - "$ref": "../transitions/transition.json", - "description": "Next transition of the workflow if there is valid matches" - }, - "end": { - "$ref": "../end/end.json", - "description": "Workflow end definition" - } - }, - "required": [ - "eventRef", - "transition" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/transitions/transition.json b/api/src/main/resources/schema/transitions/transition.json deleted file mode 100644 index c540089f..00000000 --- a/api/src/main/resources/schema/transitions/transition.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "type": "object", - "javaType": "io.serverlessworkflow.api.transitions.Transition", - "properties": { - "produceEvents": { - "type": "array", - "description": "Array of events to be produced", - "items": { - "type": "object", - "$ref": "../produce/produceevent.json" - } - }, - "nextState": { - "type": "string", - "description": "State to transition to next", - "minLength": 1 - }, - "compensate": { - "type": "boolean", - "default": false, - "description": "If set to true, triggers workflow compensation before this transition is taken. Default is false" - } - }, - "required": [ - "nextState" - ] -} \ No newline at end of file diff --git a/api/src/main/resources/schema/workflow.json b/api/src/main/resources/schema/workflow.json deleted file mode 100644 index 429e82e6..00000000 --- a/api/src/main/resources/schema/workflow.json +++ /dev/null @@ -1,136 +0,0 @@ -{ - "$id": "https://wg-serverless.org/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", - "extendsJavaClass": "io.serverlessworkflow.api.workflow.BaseWorkflow", - "javaType": "io.serverlessworkflow.api.Workflow", - "javaInterfaces": [ - "java.io.Serializable" - ], - "properties": { - "id": { - "type": "string", - "description": "Workflow unique identifier", - "minLength": 1 - }, - "name": { - "type": "string", - "description": "Workflow name", - "minLength": 1 - }, - "description": { - "type": "string", - "description": "Workflow description" - }, - "version": { - "type": "string", - "description": "Workflow version" - }, - "dataInputSchema": { - "$ref": "datainputschema/datainputschema.json", - "description": "Workflow data input schema" - }, - "start": { - "$ref": "start/start.json", - "description": "Defines workflow start" - }, - "schemaVersion": { - "type": "string", - "description": "Serverless Workflow schema version" - }, - "expressionLang": { - "type": "string", - "description": "Identifies the expression language used for workflow expressions. Default is 'jq'", - "default": "jq", - "minLength": 1 - }, - "execTimeout": { - "description": "Workflow execution timeout", - "$ref": "exectimeout/exectimeout.json" - }, - "keepActive": { - "type": "boolean", - "default": false, - "description": "If 'true', workflow instances is not terminated when there are no active execution paths. Instance can be terminated via 'terminate end definition' or reaching defined 'execTimeout'" - }, - "metadata": { - "$ref": "metadata/metadata.json" - }, - "events": { - "type": "object", - "existingJavaType": "io.serverlessworkflow.api.workflow.Events", - "description": "Workflow event definitions" - }, - "functions": { - "type": "object", - "existingJavaType": "io.serverlessworkflow.api.workflow.Functions", - "description": "Workflow function definitions" - }, - "retries": { - "type": "object", - "existingJavaType": "io.serverlessworkflow.api.workflow.Retries", - "description": "Workflow retry definitions" - }, - "states": { - "type": "array", - "description": "State Definitions", - "items": { - "type": "object", - "existingJavaType": "io.serverlessworkflow.api.interfaces.State", - "anyOf": [ - { - "title": "Delay State", - "$ref": "states/delaystate.json" - }, - { - "title": "Event State", - "$ref": "states/eventstate.json" - }, - { - "title": "Operation State", - "$ref": "states/operationstate.json" - }, - { - "title": "Parallel State", - "$ref": "states/parallelstate.json" - }, - { - "title": "Switch State", - "$ref": "states/switchstate.json" - }, - { - "title": "SubFlow State", - "$ref": "states/subflowstate.json" - }, - { - "title": "Relay State", - "$ref": "states/injectstate.json" - }, - { - "title": "ForEach State", - "$ref": "states/foreachstate.json" - }, - { - "title": "Callback State", - "$ref": "states/callbackstate.json" - } - ] - } - }, - "extensions": { - "type": "array", - "description": "Workflow Extensions", - "items": { - "type": "object", - "existingJavaType": "io.serverlessworkflow.api.interfaces.Extension" - } - } - }, - "required": [ - "id", - "name", - "version", - "states" - ] -} \ No newline at end of file diff --git a/api/src/test/java/io/serverlessworkflow/api/ApiTest.java b/api/src/test/java/io/serverlessworkflow/api/ApiTest.java new file mode 100644 index 00000000..ba0aefc3 --- /dev/null +++ b/api/src/test/java/io/serverlessworkflow/api/ApiTest.java @@ -0,0 +1,76 @@ +/* + * 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; + +import static io.serverlessworkflow.api.WorkflowReader.readWorkflowFromClasspath; +import static org.assertj.core.api.Assertions.assertThat; + +import io.serverlessworkflow.api.types.CallFunction; +import io.serverlessworkflow.api.types.CallHTTP; +import io.serverlessworkflow.api.types.CallTask; +import io.serverlessworkflow.api.types.HTTPArguments; +import io.serverlessworkflow.api.types.Task; +import io.serverlessworkflow.api.types.Workflow; +import java.io.IOException; +import org.junit.jupiter.api.Test; + +public class ApiTest { + + @Test + void testCallHTTPAPI() throws IOException { + Workflow workflow = readWorkflowFromClasspath("features/callHttp.yaml"); + assertThat(workflow.getDo()).isNotEmpty(); + assertThat(workflow.getDo().get(0).getName()).isNotNull(); + assertThat(workflow.getDo().get(0).getTask()).isNotNull(); + Task task = workflow.getDo().get(0).getTask(); + if (task.get() instanceof CallTask) { + CallTask callTask = task.getCallTask(); + assertThat(callTask).isNotNull(); + assertThat(task.getDoTask()).isNull(); + CallHTTP httpCall = callTask.getCallHTTP(); + assertThat(httpCall).isNotNull(); + assertThat(callTask.getCallAsyncAPI()).isNull(); + HTTPArguments httpParams = httpCall.getWith(); + assertThat(httpParams.getMethod()).isEqualTo("get"); + assertThat( + httpParams + .getEndpoint() + .getEndpointConfiguration() + .getUri() + .getLiteralEndpointURI() + .getLiteralUriTemplate()) + .isEqualTo("https://petstore.swagger.io/v2/pet/{petId}"); + } + } + + @Test + void testCallFunctionAPIWithoutArguments() throws IOException { + Workflow workflow = readWorkflowFromClasspath("features/callFunction.yaml"); + assertThat(workflow.getDo()).isNotEmpty(); + assertThat(workflow.getDo().get(0).getName()).isNotNull(); + assertThat(workflow.getDo().get(0).getTask()).isNotNull(); + Task task = workflow.getDo().get(0).getTask(); + CallTask callTask = task.getCallTask(); + assertThat(callTask).isNotNull(); + assertThat(callTask.get()).isInstanceOf(CallFunction.class); + if (callTask.get() instanceof CallFunction) { + CallFunction functionCall = callTask.getCallFunction(); + assertThat(functionCall).isNotNull(); + assertThat(callTask.getCallAsyncAPI()).isNull(); + assertThat(functionCall.getWith()).isNull(); + } + } +} diff --git a/api/src/test/java/io/serverlessworkflow/api/FeaturesTest.java b/api/src/test/java/io/serverlessworkflow/api/FeaturesTest.java new file mode 100644 index 00000000..39d7045b --- /dev/null +++ b/api/src/test/java/io/serverlessworkflow/api/FeaturesTest.java @@ -0,0 +1,91 @@ +/* + * 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; + +import static io.serverlessworkflow.api.WorkflowReader.readWorkflow; +import static io.serverlessworkflow.api.WorkflowReader.readWorkflowFromClasspath; +import static io.serverlessworkflow.api.WorkflowReader.validation; +import static io.serverlessworkflow.api.WorkflowWriter.workflowAsBytes; +import static io.serverlessworkflow.api.WorkflowWriter.workflowAsString; +import static io.serverlessworkflow.api.WorkflowWriter.writeWorkflow; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import io.serverlessworkflow.api.types.Workflow; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class FeaturesTest { + + @ParameterizedTest + @ValueSource( + strings = { + "features/authentication-bearer.yaml", + "features/authentication-bearer-uri-format.yaml", + "features/authentication-oauth2.yaml", + "features/authentication-oauth2-secret.yaml", + "features/authentication-oidc.yaml", + "features/authentication-oidc-secret.yaml", + "features/authentication-reusable.yaml", + "features/callHttp.yaml", + "features/callOpenAPI.yaml", + "features/composite.yaml", + "features/data-flow.yaml", + "features/emit.yaml", + "features/flow.yaml", + "features/for.yaml", + "features/raise.yaml", + "features/set.yaml", + "features/switch.yaml", + "features/try.yaml", + "features/listen-to-any.yaml", + "features/callFunction.yaml", + "features/callCustomFunction.yaml", + "features/call-http-query-parameters.yaml" + }) + public void testSpecFeaturesParsing(String workflowLocation) throws IOException { + Workflow workflow = readWorkflowFromClasspath(workflowLocation, validation()); + assertWorkflow(workflow); + assertWorkflowEquals(workflow, writeAndReadInMemory(workflow)); + } + + private static Workflow writeAndReadInMemory(Workflow workflow) throws IOException { + byte[] bytes; + try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { + writeWorkflow(out, workflow, WorkflowFormat.JSON); + bytes = out.toByteArray(); + } + try (ByteArrayInputStream in = new ByteArrayInputStream(bytes)) { + return readWorkflow(in, WorkflowFormat.JSON); + } + } + + private static void assertWorkflow(Workflow workflow) { + assertNotNull(workflow); + assertNotNull(workflow.getDocument()); + assertNotNull(workflow.getDo()); + } + + private static void assertWorkflowEquals(Workflow workflow, Workflow other) throws IOException { + assertThat(workflowAsString(workflow, WorkflowFormat.YAML)) + .isEqualTo(workflowAsString(other, WorkflowFormat.YAML)); + assertThat(workflowAsBytes(workflow, WorkflowFormat.JSON)) + .isEqualTo(workflowAsBytes(other, WorkflowFormat.JSON)); + } +} diff --git a/api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java b/api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java deleted file mode 100644 index 9343f6cc..00000000 --- a/api/src/test/java/io/serverlessworkflow/api/test/MarkupToWorkflowTest.java +++ /dev/null @@ -1,498 +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.test; - -import com.fasterxml.jackson.databind.JsonNode; -import io.serverlessworkflow.api.Workflow; -import io.serverlessworkflow.api.actions.Action; -import io.serverlessworkflow.api.datainputschema.DataInputSchema; -import io.serverlessworkflow.api.defaultdef.DefaultDefinition; -import io.serverlessworkflow.api.exectimeout.ExecTimeout; -import io.serverlessworkflow.api.functions.FunctionDefinition; -import io.serverlessworkflow.api.functions.FunctionRef; -import io.serverlessworkflow.api.interfaces.State; -import io.serverlessworkflow.api.retry.RetryDefinition; -import io.serverlessworkflow.api.states.EventState; -import io.serverlessworkflow.api.states.OperationState; -import io.serverlessworkflow.api.states.SubflowState; -import io.serverlessworkflow.api.states.SwitchState; -import io.serverlessworkflow.api.switchconditions.DataCondition; -import io.serverlessworkflow.api.test.utils.WorkflowTestUtils; -import io.serverlessworkflow.api.workflow.Retries; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; - -public class MarkupToWorkflowTest { - - @ParameterizedTest - @ValueSource(strings = {"/examples/applicantrequest.json", "/examples/applicantrequest.yml", - "/examples/carauctionbids.json", "/examples/carauctionbids.yml", - "/examples/creditcheck.json", "/examples/creditcheck.yml", - "/examples/eventbasedgreeting.json", "/examples/eventbasedgreeting.yml", - "/examples/finalizecollegeapplication.json", "/examples/finalizecollegeapplication.yml", - "/examples/greeting.json", "/examples/greeting.yml", - "/examples/helloworld.json", "/examples/helloworld.yml", - "/examples/jobmonitoring.json", "/examples/jobmonitoring.yml", - "/examples/monitorpatient.json", "/examples/monitorpatient.yml", - "/examples/parallel.json", "/examples/parallel.yml", - "/examples/provisionorder.json", "/examples/provisionorder.yml", - "/examples/sendcloudevent.json", "/examples/sendcloudevent.yml", - "/examples/solvemathproblems.json", "/examples/solvemathproblems.yml", - "/examples/foreachstatewithactions.json", "/examples/foreachstatewithactions.yml", - "/examples/periodicinboxcheck.json", "/examples/periodicinboxcheck.yml", - "/examples/vetappointmentservice.json", "/examples/vetappointmentservice.yml", - "/examples/eventbasedtransition.json", "/examples/eventbasedtransition.yml", - "/examples/roomreadings.json", "/examples/roomreadings.yml", - "/examples/checkcarvitals.json", "/examples/checkcarvitals.yml", - "/examples/booklending.json", "/examples/booklending.yml" - }) - public void testSpecExamplesParsing(String workflowLocation) { - Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); - - assertNotNull(workflow); - assertNotNull(workflow.getId()); - assertNotNull(workflow.getName()); - assertNotNull(workflow.getStates()); - assertTrue(workflow.getStates().size() > 0); - } - - @ParameterizedTest - @ValueSource(strings = {"/features/applicantrequest.json", "/features/applicantrequest.yml"}) - public void testSpecFreatureFunctionRef(String workflowLocation) { - Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); - - assertNotNull(workflow); - assertNotNull(workflow.getId()); - assertNotNull(workflow.getName()); - assertNotNull(workflow.getStates()); - assertTrue(workflow.getStates().size() > 0); - - assertNotNull(workflow.getFunctions()); - assertEquals(1, workflow.getFunctions().getFunctionDefs().size()); - - assertNotNull(workflow.getRetries()); - assertEquals(1, workflow.getRetries().getRetryDefs().size()); - } - - @ParameterizedTest - @ValueSource(strings = {"/features/vetappointment.json", "/features/vetappointment.yml"}) - public void testSpecFreatureEventRef(String workflowLocation) { - Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); - - assertNotNull(workflow); - assertNotNull(workflow.getId()); - assertNotNull(workflow.getName()); - assertNotNull(workflow.getStates()); - assertTrue(workflow.getStates().size() > 0); - - assertNotNull(workflow.getEvents()); - assertEquals(2, workflow.getEvents().getEventDefs().size()); - - assertNotNull(workflow.getRetries()); - assertEquals(1, workflow.getRetries().getRetryDefs().size()); - } - - @ParameterizedTest - @ValueSource(strings = {"/features/compensationworkflow.json", "/features/compensationworkflow.yml"}) - public void testSpecFreatureCompensation(String workflowLocation) { - Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); - - assertNotNull(workflow); - assertNotNull(workflow.getId()); - assertNotNull(workflow.getName()); - assertNotNull(workflow.getStates()); - - assertNotNull(workflow.getStates()); - assertEquals(2, workflow.getStates().size()); - - State firstState = workflow.getStates().get(0); - assertTrue(firstState instanceof EventState); - assertNotNull(firstState.getCompensatedBy()); - assertEquals("CancelPurchase", firstState.getCompensatedBy()); - - State secondState = workflow.getStates().get(1); - assertTrue(secondState instanceof OperationState); - OperationState operationState = (OperationState) secondState; - - assertTrue(operationState.isUsedForCompensation()); - } - - @ParameterizedTest - @ValueSource(strings = {"/features/functiontypes.json", "/features/functiontypes.yml"}) - public void testFunctionTypes(String workflowLocation) { - Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); - - assertNotNull(workflow); - assertNotNull(workflow.getId()); - assertNotNull(workflow.getName()); - assertNotNull(workflow.getStates()); - - assertNotNull(workflow.getStates()); - assertEquals(1, workflow.getStates().size()); - - State state = workflow.getStates().get(0); - assertTrue(state instanceof OperationState); - - List functionDefs = workflow.getFunctions().getFunctionDefs(); - assertNotNull(functionDefs); - assertEquals(2, functionDefs.size()); - - FunctionDefinition restFunc = functionDefs.get(0); - assertEquals(restFunc.getType(), FunctionDefinition.Type.REST); - - FunctionDefinition restFunc2 = functionDefs.get(1); - assertEquals(restFunc2.getType(), FunctionDefinition.Type.EXPRESSION); - } - - @ParameterizedTest - @ValueSource(strings = {"/features/transitions.json", "/features/transitions.yml"}) - public void testTransitions(String workflowLocation) { - Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); - - assertNotNull(workflow); - assertNotNull(workflow.getId()); - assertNotNull(workflow.getName()); - assertNotNull(workflow.getStates()); - - assertNotNull(workflow.getStates()); - assertEquals(1, workflow.getStates().size()); - - State state = workflow.getStates().get(0); - assertTrue(state instanceof SwitchState); - - SwitchState switchState = (SwitchState) workflow.getStates().get(0); - assertNotNull(switchState.getDataConditions()); - List dataConditions = switchState.getDataConditions(); - - assertEquals(2, dataConditions.size()); - - DataCondition cond1 = switchState.getDataConditions().get(0); - assertNotNull(cond1.getTransition()); - assertEquals("StartApplication", cond1.getTransition().getNextState()); - assertNotNull(cond1.getTransition().getProduceEvents()); - assertTrue(cond1.getTransition().getProduceEvents().isEmpty()); - assertFalse(cond1.getTransition().isCompensate()); - - - DataCondition cond2 = switchState.getDataConditions().get(1); - assertNotNull(cond2.getTransition()); - assertEquals("RejectApplication", cond2.getTransition().getNextState()); - assertNotNull(cond2.getTransition().getProduceEvents()); - assertEquals(1, cond2.getTransition().getProduceEvents().size()); - assertFalse(cond2.getTransition().isCompensate()); - - - assertNotNull(switchState.getDefault()); - DefaultDefinition defaultDefinition = switchState.getDefault(); - assertNotNull(defaultDefinition.getTransition()); - assertEquals("RejectApplication", defaultDefinition.getTransition().getNextState()); - assertNotNull(defaultDefinition.getTransition().getProduceEvents()); - assertTrue(defaultDefinition.getTransition().getProduceEvents().isEmpty()); - assertTrue(defaultDefinition.getTransition().isCompensate()); - - } - - @ParameterizedTest - @ValueSource(strings = {"/features/functionrefs.json", "/features/functionrefs.yml"}) - public void testFunctionRefs(String workflowLocation) { - Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); - - assertNotNull(workflow); - assertNotNull(workflow.getId()); - assertNotNull(workflow.getName()); - assertNotNull(workflow.getStates()); - - assertNotNull(workflow.getStates()); - assertEquals(1, workflow.getStates().size()); - - State state = workflow.getStates().get(0); - assertTrue(state instanceof OperationState); - - OperationState operationState = (OperationState) workflow.getStates().get(0); - assertNotNull(operationState.getActions()); - assertEquals(2, operationState.getActions().size()); - - Action action1 = operationState.getActions().get(0); - assertNotNull(action1); - assertNotNull(action1.getFunctionRef()); - FunctionRef functionRef1 = action1.getFunctionRef(); - assertEquals("creditCheckFunction", functionRef1.getRefName()); - assertNull(functionRef1.getArguments()); - - Action action2 = operationState.getActions().get(1); - assertNotNull(action2); - assertNotNull(action2.getFunctionRef()); - FunctionRef functionRef2 = action2.getFunctionRef(); - assertEquals("sendRejectionEmailFunction", functionRef2.getRefName()); - assertEquals(1, functionRef2.getArguments().size()); - assertEquals("${ .customer }", functionRef2.getArguments().get("applicant").asText()); - } - - @ParameterizedTest - @ValueSource(strings = {"/features/keepactiveexectimeout.json", "/features/keepactiveexectimeout.yml"}) - public void testKeepActiveExecTimeout(String workflowLocation) { - Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); - - assertNotNull(workflow); - assertNotNull(workflow.getId()); - assertNotNull(workflow.getName()); - assertNotNull(workflow.getStates()); - - assertTrue(workflow.isKeepActive()); - assertNotNull(workflow.getExecTimeout()); - - ExecTimeout execTimeout = workflow.getExecTimeout(); - assertEquals("PT1H", execTimeout.getDuration()); - assertEquals("GenerateReport", execTimeout.getRunBefore()); - } - - @ParameterizedTest - @ValueSource(strings = {"/features/checkcarvitals.json", "/features/checkcarvitals.yml"}) - public void testSubflowStateRepeat(String workflowLocation) { - Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); - - assertNotNull(workflow); - assertNotNull(workflow.getId()); - assertNotNull(workflow.getName()); - assertNotNull(workflow.getStates()); - - assertNotNull(workflow.getStates()); - assertEquals(2, workflow.getStates().size()); - - State state = workflow.getStates().get(1); - assertTrue(state instanceof SubflowState); - - SubflowState subflowState = (SubflowState) workflow.getStates().get(1); - assertNotNull(subflowState.getRepeat()); - assertEquals(10, subflowState.getRepeat().getMax()); - assertTrue(subflowState.getRepeat().isContinueOnError()); - assertNotNull(subflowState.getRepeat().getStopOnEvents()); - assertEquals(1, subflowState.getRepeat().getStopOnEvents().size()); - assertEquals("CarTurnedOffEvent", subflowState.getRepeat().getStopOnEvents().get(0)); - } - - @ParameterizedTest - @ValueSource(strings = {"/features/functionrefjsonparams.json", "/features/functionrefjsonparams.yml"}) - public void testFunctionRefJsonParams(String workflowLocation) { - Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); - - assertNotNull(workflow); - assertNotNull(workflow.getId()); - assertNotNull(workflow.getName()); - assertNotNull(workflow.getStates()); - - assertNotNull(workflow.getStates()); - assertEquals(1, workflow.getStates().size()); - assertTrue(workflow.getStates().get(0) instanceof OperationState); - - OperationState operationState = (OperationState) workflow.getStates().get(0); - assertNotNull(operationState.getActions()); - assertEquals(1, operationState.getActions().size()); - List actions = operationState.getActions(); - assertNotNull(actions.get(0).getFunctionRef()); - assertEquals("addPet", actions.get(0).getFunctionRef().getRefName()); - - JsonNode params = actions.get(0).getFunctionRef().getArguments(); - assertNotNull(params); - assertEquals(4, params.size()); - assertEquals(123, params.get("id").intValue()); - assertEquals("My Address, 123 MyCity, MyCountry", params.get("address").asText()); - assertEquals("${ .owner.name }", params.get("owner").asText()); - assertEquals("Pluto", params.get("body").get("name").asText()); - assertEquals("${ .pet.tagnumber }", params.get("body").get("tag").asText()); - } - - @ParameterizedTest - @ValueSource(strings = {"/features/functionrefnoparams.json", "/features/functionrefnoparams.yml"}) - public void testFunctionRefNoParams(String workflowLocation) { - Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); - - assertNotNull(workflow); - assertNotNull(workflow.getId()); - assertNotNull(workflow.getName()); - assertNotNull(workflow.getStates()); - - assertNotNull(workflow.getStates()); - assertEquals(1, workflow.getStates().size()); - assertTrue(workflow.getStates().get(0) instanceof OperationState); - - OperationState operationState = (OperationState) workflow.getStates().get(0); - assertNotNull(operationState.getActions()); - assertEquals(2, operationState.getActions().size()); - List actions = operationState.getActions(); - assertNotNull(actions.get(0).getFunctionRef()); - assertNotNull(actions.get(1).getFunctionRef()); - assertEquals("addPet", actions.get(0).getFunctionRef().getRefName()); - assertEquals("addPet", actions.get(1).getFunctionRef().getRefName()); - - JsonNode params = actions.get(0).getFunctionRef().getArguments(); - assertNull(params); - JsonNode params2 = actions.get(1).getFunctionRef().getArguments(); - assertNull(params2); - } - - @ParameterizedTest - @ValueSource(strings = {"/features/simpleschedule.json", "/features/simpleschedule.yml"}) - public void testSimplifiedSchedule(String workflowLocation) { - Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); - - assertNotNull(workflow); - - assertNotNull(workflow.getStart()); - assertNotNull(workflow.getStart().getSchedule()); - - assertEquals("2020-03-20T09:00:00Z/2020-03-20T15:00:00Z", workflow.getStart().getSchedule().getInterval()); - - assertNotNull(workflow.getId()); - assertNotNull(workflow.getName()); - assertNotNull(workflow.getStates()); - - assertNotNull(workflow.getStates()); - assertEquals(1, workflow.getStates().size()); - - } - - @ParameterizedTest - @ValueSource(strings = {"/features/simplecron.json", "/features/simplecron.yml"}) - public void testSimplifiedCron(String workflowLocation) { - Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); - - assertNotNull(workflow); - - assertNotNull(workflow.getStart()); - assertNotNull(workflow.getStart().getSchedule()); - - assertEquals("0 0/15 * * * ?", workflow.getStart().getSchedule().getCron().getExpression()); - - assertNotNull(workflow.getId()); - assertNotNull(workflow.getName()); - assertNotNull(workflow.getStates()); - - assertNotNull(workflow.getStates()); - assertEquals(2, workflow.getStates().size()); - } - - @ParameterizedTest - @ValueSource(strings = {"/features/expressionlang.json", "/features/expressionlang.yml"}) - public void testExpressionLang(String workflowLocation) { - Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); - - assertNotNull(workflow); - assertNotNull(workflow.getId()); - assertNotNull(workflow.getName()); - assertNotNull(workflow.getStates()); - - assertNotNull(workflow.getExpressionLang()); - assertEquals("abc", workflow.getExpressionLang()); - } - - @ParameterizedTest - @ValueSource(strings = {"/features/shortstart.json", "/features/shortstart.yml"}) - public void testShortStart(String workflowLocation) { - Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); - - assertNotNull(workflow); - assertNotNull(workflow.getId()); - assertNotNull(workflow.getName()); - assertNotNull(workflow.getStates()); - - assertNotNull(workflow.getStart()); - assertEquals("TestFunctionRefs", workflow.getStart().getStateName()); - assertNull(workflow.getStart().getSchedule()); - - } - - @ParameterizedTest - @ValueSource(strings = {"/features/longstart.json", "/features/longstart.yml"}) - public void testLongStart(String workflowLocation) { - Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); - - assertNotNull(workflow); - assertNotNull(workflow.getId()); - assertNotNull(workflow.getName()); - assertNotNull(workflow.getStates()); - - assertNotNull(workflow.getStart()); - assertEquals("TestFunctionRefs", workflow.getStart().getStateName()); - assertNotNull(workflow.getStart().getSchedule()); - assertNotNull(workflow.getStart().getSchedule().getCron()); - assertEquals("0 0/15 * * * ?", workflow.getStart().getSchedule().getCron().getExpression()); - - } - - @ParameterizedTest - @ValueSource(strings = {"/features/retriesprops.json", "/features/retriesprops.yml"}) - public void testRetriesProps(String workflowLocation) { - Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); - - assertNotNull(workflow); - assertNotNull(workflow.getId()); - assertNotNull(workflow.getName()); - assertNotNull(workflow.getRetries()); - assertNotNull(workflow.getStates()); - - Retries retries = workflow.getRetries(); - assertNotNull(retries.getRetryDefs()); - assertEquals(1, retries.getRetryDefs().size()); - - RetryDefinition retryDefinition = retries.getRetryDefs().get(0); - assertEquals("Test Retries", retryDefinition.getName()); - assertEquals("PT1M", retryDefinition.getDelay()); - assertEquals("PT2M", retryDefinition.getMaxDelay()); - assertEquals("PT2S", retryDefinition.getIncrement()); - assertEquals("1.2", retryDefinition.getMultiplier()); - assertEquals("20", retryDefinition.getMaxAttempts()); - assertEquals("0.4", retryDefinition.getJitter()); - - } - - @ParameterizedTest - @ValueSource(strings = {"/features/datainputschemastring.json", "/features/datainputschemastring.yml"}) - public void testDataInputSchemaFromString(String workflowLocation) { - Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); - - assertNotNull(workflow); - assertNotNull(workflow.getId()); - assertNotNull(workflow.getName()); - assertNotNull(workflow.getStates()); - - DataInputSchema dataInputSchema = workflow.getDataInputSchema(); - assertNotNull(dataInputSchema); - assertEquals("somejsonschema.json", dataInputSchema.getSchema()); - assertTrue(dataInputSchema.isFailOnValidationErrors()); - } - - @ParameterizedTest - @ValueSource(strings = {"/features/datainputschemaobj.json", "/features/datainputschemaobj.yml"}) - public void testDataInputSchemaFromObject(String workflowLocation) { - Workflow workflow = Workflow.fromSource(WorkflowTestUtils.readWorkflowFile(workflowLocation)); - - assertNotNull(workflow); - assertNotNull(workflow.getId()); - assertNotNull(workflow.getName()); - assertNotNull(workflow.getStates()); - - DataInputSchema dataInputSchema = workflow.getDataInputSchema(); - assertNotNull(dataInputSchema); - assertEquals("somejsonschema.json", dataInputSchema.getSchema()); - assertFalse(dataInputSchema.isFailOnValidationErrors()); - } -} diff --git a/api/src/test/java/io/serverlessworkflow/api/test/WorkflowToMarkupTest.java b/api/src/test/java/io/serverlessworkflow/api/test/WorkflowToMarkupTest.java deleted file mode 100644 index 89559193..00000000 --- a/api/src/test/java/io/serverlessworkflow/api/test/WorkflowToMarkupTest.java +++ /dev/null @@ -1,141 +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.test; - -import io.serverlessworkflow.api.Workflow; -import io.serverlessworkflow.api.end.End; -import io.serverlessworkflow.api.events.EventDefinition; -import io.serverlessworkflow.api.functions.FunctionDefinition; -import io.serverlessworkflow.api.interfaces.State; -import io.serverlessworkflow.api.produce.ProduceEvent; -import io.serverlessworkflow.api.schedule.Schedule; -import io.serverlessworkflow.api.start.Start; -import io.serverlessworkflow.api.states.DelayState; -import io.serverlessworkflow.api.workflow.Events; -import io.serverlessworkflow.api.workflow.Functions; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; - -import static io.serverlessworkflow.api.states.DefaultState.Type.DELAY; -import static org.junit.jupiter.api.Assertions.*; - -public class WorkflowToMarkupTest { - @Test - public void testSingleState() { - - Workflow workflow = new Workflow().withId("test-workflow").withName("test-workflow-name").withVersion("1.0") - .withStart(new Start().withSchedule( - new Schedule().withInterval("PT1S") - )) - .withStates(Arrays.asList( - new DelayState().withName("delayState").withType(DELAY) - .withEnd( - new End().withTerminate(true).withCompensate(true) - .withProduceEvents(Arrays.asList( - new ProduceEvent().withEventRef("someEvent") - )) - ) - .withTimeDelay("PT1M") - ) - ); - - assertNotNull(workflow); - assertNotNull(workflow.getStart()); - assertEquals(1, workflow.getStates().size()); - State state = workflow.getStates().get(0); - assertTrue(state instanceof DelayState); - assertNotNull(state.getEnd()); - - assertNotNull(Workflow.toJson(workflow)); - assertNotNull(Workflow.toYaml(workflow)); - } - - @Test - public void testSingleFunction() { - - Workflow workflow = new Workflow().withId("test-workflow").withName("test-workflow-name").withVersion("1.0") - .withStart( - new Start() - ) - .withFunctions(new Functions(Arrays.asList( - new FunctionDefinition().withName("testFunction") - .withOperation("testSwaggerDef#testOperationId"))) - ) - .withStates(Arrays.asList( - new DelayState().withName("delayState").withType(DELAY) - .withEnd( - new End() - ) - .withTimeDelay("PT1M") - ) - ); - - assertNotNull(workflow); - assertNotNull(workflow.getStart()); - assertEquals(1, workflow.getStates().size()); - State state = workflow.getStates().get(0); - assertTrue(state instanceof DelayState); - assertNotNull(workflow.getFunctions()); - assertEquals(1, workflow.getFunctions().getFunctionDefs().size()); - assertEquals("testFunction", workflow.getFunctions().getFunctionDefs().get(0).getName()); - - assertNotNull(Workflow.toJson(workflow)); - assertNotNull(Workflow.toYaml(workflow)); - } - - @Test - public void testSingleEvent() { - - Workflow workflow = new Workflow().withId("test-workflow").withName("test-workflow-name").withVersion("1.0") - .withStart( - new Start() - ) - .withEvents(new Events(Arrays.asList( - new EventDefinition().withName("testEvent").withSource("testSource").withType("testType") - .withKind(EventDefinition.Kind.PRODUCED))) - ) - .withFunctions(new Functions(Arrays.asList( - new FunctionDefinition().withName("testFunction") - .withOperation("testSwaggerDef#testOperationId"))) - ) - .withStates(Arrays.asList( - new DelayState().withName("delayState").withType(DELAY) - .withEnd( - new End() - ) - .withTimeDelay("PT1M") - ) - ); - - assertNotNull(workflow); - assertNotNull(workflow.getStart()); - assertEquals(1, workflow.getStates().size()); - State state = workflow.getStates().get(0); - assertTrue(state instanceof DelayState); - assertNotNull(workflow.getFunctions()); - assertEquals(1, workflow.getFunctions().getFunctionDefs().size()); - assertEquals("testFunction", workflow.getFunctions().getFunctionDefs().get(0).getName()); - assertNotNull(workflow.getEvents()); - assertEquals(1, workflow.getEvents().getEventDefs().size()); - assertEquals("testEvent", workflow.getEvents().getEventDefs().get(0).getName()); - assertEquals(EventDefinition.Kind.PRODUCED, workflow.getEvents().getEventDefs().get(0).getKind()); - - assertNotNull(Workflow.toJson(workflow)); - assertNotNull(Workflow.toYaml(workflow)); - } -} diff --git a/api/src/test/java/io/serverlessworkflow/api/test/utils/WorkflowTestUtils.java b/api/src/test/java/io/serverlessworkflow/api/test/utils/WorkflowTestUtils.java deleted file mode 100644 index 31451d80..00000000 --- a/api/src/test/java/io/serverlessworkflow/api/test/utils/WorkflowTestUtils.java +++ /dev/null @@ -1,71 +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.test.utils; - -import io.serverlessworkflow.api.mapper.JsonObjectMapper; -import io.serverlessworkflow.api.mapper.YamlObjectMapper; - -import java.io.*; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -public class WorkflowTestUtils { - private static JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(); - private static YamlObjectMapper yamlObjectMapper = new YamlObjectMapper(); - - public static final Path resourceDirectory = Paths.get("src", - "test", - "resources"); - public static final String absolutePath = resourceDirectory.toFile().getAbsolutePath(); - - public static Path getResourcePath(String file) { - return Paths.get(absolutePath + File.separator + file); - } - - public static InputStream getInputStreamFromPath(Path path) throws Exception { - return Files.newInputStream(path); - } - - public static String readWorkflowFile(String location) { - return readFileAsString(classpathResourceReader(location)); - } - - public static Reader classpathResourceReader(String location) { - return new InputStreamReader(WorkflowTestUtils.class.getResourceAsStream(location)); - } - - public static String readFileAsString(Reader reader) { - try { - StringBuilder fileData = new StringBuilder(1000); - char[] buf = new char[1024]; - int numRead; - while ((numRead = reader.read(buf)) != -1) { - String readData = String.valueOf(buf, - 0, - numRead); - fileData.append(readData); - buf = new char[1024]; - } - reader.close(); - return fileData.toString(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - -} diff --git a/api/src/test/resources/examples/applicantrequest.json b/api/src/test/resources/examples/applicantrequest.json deleted file mode 100644 index b330bdeb..00000000 --- a/api/src/test/resources/examples/applicantrequest.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "id": "applicantrequest", - "version": "1.0", - "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" - } - ], - "default": { - "transition": "RejectApplication" - } - }, - { - "name": "StartApplication", - "type": "subflow", - "workflowId": "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/examples/applicantrequest.yml b/api/src/test/resources/examples/applicantrequest.yml deleted file mode 100644 index d75d400f..00000000 --- a/api/src/test/resources/examples/applicantrequest.yml +++ /dev/null @@ -1,31 +0,0 @@ -id: applicantrequest -version: '1.0' -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 - default: - transition: RejectApplication - - name: StartApplication - type: subflow - workflowId: 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/examples/booklending.json b/api/src/test/resources/examples/booklending.json deleted file mode 100644 index 6260ae8b..00000000 --- a/api/src/test/resources/examples/booklending.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "id": "booklending", - "name": "Book Lending Workflow", - "version": "1.0", - "start": "Book Lending Request", - "states": [ - { - "name": "Book Lending Request", - "type": "event", - "onEvents": [ - { - "eventRefs": ["Book Lending Request Event"] - } - ], - "transition": "Get Book Status" - }, - { - "name": "Get Book Status", - "type": "operation", - "actions": [ - { - "functionRef": { - "refName": "Get status for book", - "arguments": { - "bookid": "${ .book.id }" - } - } - } - ], - "transition": "Book Status Decision" - }, - { - "name": "Book Status Decision", - "type": "switch", - "dataConditions": [ - { - "name": "Book is on loan", - "condition": "${ .book.status == \"onloan\" }", - "transition": "Report Status To Lender" - }, - { - "name": "Check is available", - "condition": "${ .book.status == \"available\" }", - "transition": "Check Out Book" - } - ] - }, - { - "name": "Report Status To Lender", - "type": "operation", - "actions": [ - { - "functionRef": { - "refName": "Send status to lender", - "arguments": { - "bookid": "${ .book.id }", - "message": "Book ${ .book.title } is already on loan" - } - } - } - ], - "transition": "Wait for Lender response" - }, - { - "name": "Wait for Lender response", - "type": "switch", - "eventConditions": [ - { - "name": "Hold Book", - "eventRef": "Hold Book Event", - "transition": "Request Hold" - }, - { - "name": "Decline Book Hold", - "eventRef": "Decline Hold Event", - "transition": "Cancel Request" - } - ] - }, - { - "name": "Request Hold", - "type": "operation", - "actions": [ - { - "functionRef": { - "refName": "Request hold for lender", - "arguments": { - "bookid": "${ .book.id }", - "lender": "${ .lender }" - } - } - } - ], - "transition": "Wait two weeks" - }, - { - "name": "Wait two weeks", - "type": "delay", - "timeDelay": "P2W", - "transition": "Get Book Status" - }, - { - "name": "Check Out Book", - "type": "operation", - "actions": [ - { - "functionRef": { - "refName": "Check out book with id", - "arguments": { - "bookid": "${ .book.id }" - } - } - }, - { - "functionRef": { - "refName": "Notify Lender for checkout", - "arguments": { - "bookid": "${ .book.id }", - "lender": "${ .lender }" - } - } - } - ], - "end": true - } - ], - "functions": "file://books/lending/functions.json", - "events": "file://books/lending/events.json" -} \ No newline at end of file diff --git a/api/src/test/resources/examples/booklending.yml b/api/src/test/resources/examples/booklending.yml deleted file mode 100644 index e7e3e525..00000000 --- a/api/src/test/resources/examples/booklending.yml +++ /dev/null @@ -1,74 +0,0 @@ -id: booklending -name: Book Lending Workflow -version: '1.0' -start: Book Lending Request -states: - - name: Book Lending Request - type: event - onEvents: - - eventRefs: - - Book Lending Request Event - transition: Get Book Status - - name: Get Book Status - type: operation - actions: - - functionRef: - refName: Get status for book - arguments: - bookid: "${ .book.id }" - transition: Book Status Decision - - name: Book Status Decision - type: switch - dataConditions: - - name: Book is on loan - condition: ${ .book.status == "onloan" } - transition: Report Status To Lender - - name: Check is available - condition: ${ .book.status == "available" } - transition: Check Out Book - - name: Report Status To Lender - type: operation - actions: - - functionRef: - refName: Send status to lender - arguments: - bookid: "${ .book.id }" - message: Book ${ .book.title } is already on loan - transition: Wait for Lender response - - name: Wait for Lender response - type: switch - eventConditions: - - name: Hold Book - eventRef: Hold Book Event - transition: Request Hold - - name: Decline Book Hold - eventRef: Decline Hold Event - transition: Cancel Request - - name: Request Hold - type: operation - actions: - - functionRef: - refName: Request fold for lender - arguments: - bookid: "${ .book.id }" - lender: "${ .lender }" - transition: Wait two weeks - - name: Wait two weeks - type: delay - timeDelay: P2W - transition: Get Book Status - - name: Check Out Book - type: operation - actions: - - functionRef: - refName: Check out book with id - arguments: - bookid: "${ .book.id }" - - functionRef: - refName: Notify Lender for checkout - arguments: - bookid: "${ .book.id }" - lender: "${ .lender }" - end: true -functions: file://books/lending/functions.json -events: file://books/lending/events.json \ No newline at end of file diff --git a/api/src/test/resources/examples/carauctionbids.json b/api/src/test/resources/examples/carauctionbids.json deleted file mode 100644 index 8c2fde11..00000000 --- a/api/src/test/resources/examples/carauctionbids.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "id": "handleCarAuctionBid", - "version": "1.0", - "name": "Car Auction Bidding Workflow", - "description": "Store a single bid whole the car auction is active", - "start": { - "stateName": "StoreCarAuctionBid", - "schedule": "2020-03-20T09:00:00Z/2020-03-20T15:00:00Z" - }, - "functions": [ - { - "name": "StoreBidFunction", - "operation": "http://myapis.org/carauctionapi.json#storeBid" - } - ], - "events": [ - { - "name": "CarBidEvent", - "type": "carBidMadeType", - "source": "carBidEventSource" - } - ], - "states": [ - { - "name": "StoreCarAuctionBid", - "type": "event", - "exclusive": true, - "onEvents": [ - { - "eventRefs": ["CarBidEvent"], - "actions": [{ - "functionRef": { - "refName": "StoreBidFunction", - "arguments": { - "bid": "${ .bid }" - } - } - }] - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/examples/carauctionbids.yml b/api/src/test/resources/examples/carauctionbids.yml deleted file mode 100644 index f943f52f..00000000 --- a/api/src/test/resources/examples/carauctionbids.yml +++ /dev/null @@ -1,27 +0,0 @@ -id: handleCarAuctionBid -version: '1.0' -name: Car Auction Bidding Workflow -description: Store a single bid whole the car auction is active -start: - stateName: StoreCarAuctionBid - schedule: 2020-03-20T09:00:00Z/2020-03-20T15:00:00Z -functions: - - name: StoreBidFunction - operation: http://myapis.org/carauctionapi.json#storeBid -events: - - name: CarBidEvent - type: carBidMadeType - source: carBidEventSource -states: - - name: StoreCarAuctionBid - type: event - exclusive: true - onEvents: - - eventRefs: - - CarBidEvent - actions: - - functionRef: - refName: StoreBidFunction - arguments: - bid: "${ .bid }" - end: true \ No newline at end of file diff --git a/api/src/test/resources/examples/checkcarvitals.json b/api/src/test/resources/examples/checkcarvitals.json deleted file mode 100644 index 504235d2..00000000 --- a/api/src/test/resources/examples/checkcarvitals.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "id": "checkcarvitals", - "name": "Check Car Vitals Workflow", - "version": "1.0", - "start": "WhenCarIsOn", - "states": [ - { - "name": "WhenCarIsOn", - "type": "event", - "onEvents": [ - { - "eventRefs": ["CarTurnedOnEvent"] - } - ], - "transition": "DoCarVitalsChecks" - }, - { - "name": "DoCarVitalsChecks", - "type": "subflow", - "workflowId": "vitalscheck", - "repeat": { - "stopOnEvents": ["CarTurnedOffEvent"] - }, - "end": true - } - ], - "events": [ - { - "name": "CarTurnedOnEvent", - "type": "car.events", - "source": "my/car/start" - }, - { - "name": "CarTurnedOffEvent", - "type": "car.events", - "source": "my/car/start" - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/examples/checkcarvitals.yml b/api/src/test/resources/examples/checkcarvitals.yml deleted file mode 100644 index d17d73ce..00000000 --- a/api/src/test/resources/examples/checkcarvitals.yml +++ /dev/null @@ -1,25 +0,0 @@ -id: checkcarvitals -name: Check Car Vitals Workflow -version: '1.0' -start: WhenCarIsOn -states: - - name: WhenCarIsOn - type: event - onEvents: - - eventRefs: - - CarTurnedOnEvent - transition: DoCarVitalsChecks - - name: DoCarVitalsChecks - type: subflow - workflowId: vitalscheck - repeat: - stopOnEvents: - - CarTurnedOffEvent - end: true -events: - - name: CarTurnedOnEvent - type: car.events - source: my/car/start - - name: CarTurnedOffEvent - type: car.events - source: my/car/start \ No newline at end of file diff --git a/api/src/test/resources/examples/creditcheck.json b/api/src/test/resources/examples/creditcheck.json deleted file mode 100644 index e6bfd9b6..00000000 --- a/api/src/test/resources/examples/creditcheck.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "id": "customercreditcheck", - "version": "1.0", - "name": "Customer Credit Check Workflow", - "description": "Perform Customer Credit Check", - "start": "CheckCredit", - "functions": [ - { - "name": "creditCheckFunction", - "operation": "http://myapis.org/creditcheckapi.json#doCreditCheck" - }, - { - "name": "sendRejectionEmailFunction", - "operation": "http://myapis.org/creditcheckapi.json#rejectionEmail" - } - ], - "events": [ - { - "name": "CreditCheckCompletedEvent", - "type": "creditCheckCompleteType", - "source": "creditCheckSource", - "correlation": [ - { - "contextAttributeName": "customerId" - } - ] - } - ], - "states": [ - { - "name": "CheckCredit", - "type": "callback", - "action": { - "functionRef": { - "refName": "callCreditCheckMicroservice", - "arguments": { - "customer": "${ .customer }" - } - } - }, - "eventRef": "CreditCheckCompletedEvent", - "timeout": "PT15M", - "transition": "EvaluateDecision" - }, - { - "name": "EvaluateDecision", - "type": "switch", - "dataConditions": [ - { - "condition": "${ .creditCheck | .decision == \"Approved\" }", - "transition": "StartApplication'" - }, - { - "condition": "${ .creditCheck | .decision == \"Denied\" }", - "transition": "RejectApplication" - } - ], - "default": { - "transition": "RejectApplication" - } - }, - { - "name": "StartApplication", - "type": "subflow", - "workflowId": "startApplicationWorkflowId", - "end": true - }, - { - "name": "RejectApplication", - "type": "operation", - "actionMode": "sequential", - "actions": [ - { - "functionRef": { - "refName": "sendRejectionEmailFunction", - "arguments": { - "applicant": "${ .customer }" - } - } - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/examples/creditcheck.yml b/api/src/test/resources/examples/creditcheck.yml deleted file mode 100644 index c4e5eb4a..00000000 --- a/api/src/test/resources/examples/creditcheck.yml +++ /dev/null @@ -1,49 +0,0 @@ -id: customercreditcheck -version: '1.0' -name: Customer Credit Check Workflow -description: Perform Customer Credit Check -start: CheckCredit -functions: - - name: creditCheckFunction - operation: http://myapis.org/creditcheckapi.json#doCreditCheck - - name: sendRejectionEmailFunction - operation: http://myapis.org/creditcheckapi.json#rejectionEmail -events: - - name: CreditCheckCompletedEvent - type: creditCheckCompleteType - source: creditCheckSource - correlation: - - contextAttributeName: customerId -states: - - name: CheckCredit - type: callback - action: - functionRef: - refName: callCreditCheckMicroservice - arguments: - customer: "${ .customer }" - eventRef: CreditCheckCompletedEvent - timeout: PT15M - transition: EvaluateDecision - - name: EvaluateDecision - type: switch - dataConditions: - - condition: "${ .creditCheck | .decision == \"Approved\" }" - transition: StartApplication - - condition: "${ .creditCheck | .decision == \"Denied\" }" - transition: RejectApplication - default: - transition: RejectApplication - - name: StartApplication - type: subflow - workflowId: startApplicationWorkflowId - end: true - - name: RejectApplication - type: operation - actionMode: sequential - actions: - - functionRef: - refName: sendRejectionEmailFunction - arguments: - applicant: "${ .customer }" - end: true \ No newline at end of file diff --git a/api/src/test/resources/examples/eventbasedgreeting.json b/api/src/test/resources/examples/eventbasedgreeting.json deleted file mode 100644 index aa80a3b4..00000000 --- a/api/src/test/resources/examples/eventbasedgreeting.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "id": "eventbasedgreeting", - "version": "1.0", - "name": "Event Based Greeting Workflow", - "description": "Event Based Greeting", - "start": "Greet", - "events": [ - { - "name": "GreetingEvent", - "type": "greetingEventType", - "source": "greetingEventSource" - } - ], - "functions": [ - { - "name": "greetingFunction", - "operation": "file://myapis/greetingapis.json#greeting" - } - ], - "states":[ - { - "name":"Greet", - "type":"event", - "onEvents": [{ - "eventRefs": ["GreetingEvent"], - "eventDataFilter": { - "data": "${ .data.greet }" - }, - "actions":[ - { - "functionRef": { - "refName": "greetingFunction", - "arguments": { - "name": "${ .greet.name }" - } - } - } - ] - }], - "stateDataFilter": { - "output": "${ .payload.greeting }" - }, - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/examples/eventbasedgreeting.yml b/api/src/test/resources/examples/eventbasedgreeting.yml deleted file mode 100644 index ec22bec3..00000000 --- a/api/src/test/resources/examples/eventbasedgreeting.yml +++ /dev/null @@ -1,28 +0,0 @@ -id: eventbasedgreeting -version: '1.0' -name: Event Based Greeting Workflow -description: Event Based Greeting -start: Greet -events: - - name: GreetingEvent - type: greetingEventType - source: greetingEventSource -functions: - - name: greetingFunction - operation: file://myapis/greetingapis.json#greeting -states: - - name: Greet - type: event - onEvents: - - eventRefs: - - GreetingEvent - eventDataFilter: - data: "${ .data.greet }" - actions: - - functionRef: - refName: greetingFunction - arguments: - name: "${ .greet.name }" - stateDataFilter: - output: "${ .payload.greeting }" - end: true \ No newline at end of file diff --git a/api/src/test/resources/examples/eventbasedtransition.json b/api/src/test/resources/examples/eventbasedtransition.json deleted file mode 100644 index 29ee0271..00000000 --- a/api/src/test/resources/examples/eventbasedtransition.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "id": "eventbasedswitch", - "version": "1.0", - "name": "Event Based Switch Transitions", - "description": "Event Based Switch Transitions", - "start": "CheckVisaStatus", - "events": [ - { - "name": "visaApprovedEvent", - "type": "VisaApproved", - "source": "visaCheckSource" - }, - { - "name": "visaRejectedEvent", - "type": "VisaRejected", - "source": "visaCheckSource" - } - ], - "states":[ - { - "name":"CheckVisaStatus", - "type":"switch", - "eventConditions": [ - { - "eventRef": "visaApprovedEvent", - "transition": "HandleApprovedVisa" - }, - { - "eventRef": "visaRejectedEvent", - "transition": "HandleRejectedVisa" - } - ], - "eventTimeout": "PT1H", - "default": { - "transition": "HandleNoVisaDecision" - } - }, - { - "name": "HandleApprovedVisa", - "type": "subflow", - "workflowId": "handleApprovedVisaWorkflowID", - "end": true - }, - { - "name": "HandleRejectedVisa", - "type": "subflow", - "workflowId": "handleRejectedVisaWorkflowID", - "end": true - }, - { - "name": "HandleNoVisaDecision", - "type": "subflow", - "workflowId": "handleNoVisaDecisionWorkfowId", - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/examples/eventbasedtransition.yml b/api/src/test/resources/examples/eventbasedtransition.yml deleted file mode 100644 index c00eef5d..00000000 --- a/api/src/test/resources/examples/eventbasedtransition.yml +++ /dev/null @@ -1,35 +0,0 @@ -id: eventbasedswitch -version: '1.0' -name: Event Based Switch Transitions -description: Event Based Switch Transitions -start: CheckVisaStatus -events: - - name: visaApprovedEvent - type: VisaApproved - source: visaCheckSource - - name: visaRejectedEvent - type: VisaRejected - source: visaCheckSource -states: - - name: CheckVisaStatus - type: switch - eventConditions: - - eventRef: visaApprovedEvent - transition: HandleApprovedVisa - - eventRef: visaRejectedEvent - transition: HandleRejectedVisa - eventTimeout: PT1H - default: - transition: HandleNoVisaDecision - - name: HandleApprovedVisa - type: subflow - workflowId: handleApprovedVisaWorkflowID - end: true - - name: HandleRejectedVisa - type: subflow - workflowId: handleRejectedVisaWorkflowID - end: true - - name: HandleNoVisaDecision - type: subflow - workflowId: handleNoVisaDecisionWorkfowId - end: true \ No newline at end of file diff --git a/api/src/test/resources/examples/finalizecollegeapplication.json b/api/src/test/resources/examples/finalizecollegeapplication.json deleted file mode 100644 index 4df99b15..00000000 --- a/api/src/test/resources/examples/finalizecollegeapplication.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "id": "finalizeCollegeApplication", - "name": "Finalize College Application", - "version": "1.0", - "start": "FinalizeApplication", - "events": [ - { - "name": "ApplicationSubmitted", - "type": "org.application.submitted", - "source": "applicationsource", - "correlation": [ - { - "contextAttributeName": "applicantId" - } - ] - }, - { - "name": "SATScoresReceived", - "type": "org.application.satscores", - "source": "applicationsource", - "correlation": [ - { - "contextAttributeName": "applicantId" - } - ] - }, - { - "name": "RecommendationLetterReceived", - "type": "org.application.recommendationLetter", - "source": "applicationsource", - "correlation": [ - { - "contextAttributeName": "applicantId" - } - ] - } - ], - "functions": [ - { - "name": "finalizeApplicationFunction", - "operation": "http://myapis.org/collegeapplicationapi.json#finalize" - } - ], - "states": [ - { - "name": "FinalizeApplication", - "type": "event", - "exclusive": false, - "onEvents": [ - { - "eventRefs": [ - "ApplicationSubmitted", - "SATScoresReceived", - "RecommendationLetterReceived" - ], - "actions": [ - { - "functionRef": { - "refName": "finalizeApplicationFunction", - "arguments": { - "student": "${ .applicantId }" - } - } - } - ] - } - ], - "end": { - "terminate": true - } - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/examples/finalizecollegeapplication.yml b/api/src/test/resources/examples/finalizecollegeapplication.yml deleted file mode 100644 index d84690ca..00000000 --- a/api/src/test/resources/examples/finalizecollegeapplication.yml +++ /dev/null @@ -1,39 +0,0 @@ -id: finalizeCollegeApplication -name: Finalize College Application -version: '1.0' -start: FinalizeApplication -events: - - name: ApplicationSubmitted - type: org.application.submitted - source: applicationsource - correlation: - - contextAttributeName: applicantId - - name: SATScoresReceived - type: org.application.satscores - source: applicationsource - correlation: - - contextAttributeName: applicantId - - name: RecommendationLetterReceived - type: org.application.recommendationLetter - source: applicationsource - correlation: - - contextAttributeName: applicantId -functions: - - name: finalizeApplicationFunction - operation: http://myapis.org/collegeapplicationapi.json#finalize -states: - - name: FinalizeApplication - type: event - exclusive: false - onEvents: - - eventRefs: - - ApplicationSubmitted - - SATScoresReceived - - RecommendationLetterReceived - actions: - - functionRef: - refName: finalizeApplicationFunction - arguments: - student: "${ .applicantId }" - end: - terminate: true \ No newline at end of file diff --git a/api/src/test/resources/examples/foreachstatewithactions.json b/api/src/test/resources/examples/foreachstatewithactions.json deleted file mode 100644 index dca06385..00000000 --- a/api/src/test/resources/examples/foreachstatewithactions.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "id": "foreachstatewithactions", - "name": "ForEach State With Actions", - "description": "ForEach State With Actions", - "version": "1.0", - "functions": [ - { - "name": "sendConfirmationFunction", - "operation": "http://myapis.org/confirmationapi.json#sendConfirmation" - } - ], - "states": [ - { - "name":"SendConfirmationForEachCompletedhOrder", - "type":"foreach", - "inputCollection": "${ .orders[?(@.completed == true)] }", - "iterationParam": "${ .completedorder }", - "actions":[ - { - "functionRef": { - "refName": "sendConfirmationFunction", - "arguments": { - "orderNumber": "${ .completedorder.orderNumber }", - "email": "${ .completedorder.email }" - } - } - }], - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/examples/foreachstatewithactions.yml b/api/src/test/resources/examples/foreachstatewithactions.yml deleted file mode 100644 index 5dddc96a..00000000 --- a/api/src/test/resources/examples/foreachstatewithactions.yml +++ /dev/null @@ -1,20 +0,0 @@ ---- -id: foreachstatewithactions -name: ForEach State With Actions -description: ForEach State With Actions -version: '1.0' -functions: - - name: sendConfirmationFunction - operation: http://myapis.org/confirmationapi.json#sendConfirmation -states: - - name: SendConfirmationForEachCompletedhOrder - type: foreach - inputCollection: "${ .orders[?(@.completed == true)] }" - iterationParam: "${ .completedorder }" - actions: - - functionRef: - refName: sendConfirmationFunction - arguments: - orderNumber: "${ .completedorder.orderNumber }" - email: "${ .completedorder.email }" - end: true diff --git a/api/src/test/resources/examples/greeting.json b/api/src/test/resources/examples/greeting.json deleted file mode 100644 index 3540cb6d..00000000 --- a/api/src/test/resources/examples/greeting.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "id": "greeting", - "version": "1.0", - "name": "Greeting Workflow", - "description": "Greet Someone", - "start": "Greet", - "functions": [ - { - "name": "greetingFunction", - "operation": "file://myapis/greetingapis.json#greeting" - } - ], - "states":[ - { - "name":"Greet", - "type":"operation", - "actions":[ - { - "functionRef": { - "refName": "greetingFunction", - "arguments": { - "name": "${ .person.name }" - } - }, - "actionDataFilter": { - "results": "${ .greeting }" - } - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/examples/greeting.yml b/api/src/test/resources/examples/greeting.yml deleted file mode 100644 index 8bbc8b4d..00000000 --- a/api/src/test/resources/examples/greeting.yml +++ /dev/null @@ -1,19 +0,0 @@ -id: greeting -version: '1.0' -name: Greeting Workflow -description: Greet Someone -start: Greet -functions: - - name: greetingFunction - operation: file://myapis/greetingapis.json#greeting -states: - - name: Greet - type: operation - actions: - - functionRef: - refName: greetingFunction - arguments: - name: "${ .person.name }" - actionDataFilter: - results: "${ .greeting }" - end: true \ No newline at end of file diff --git a/api/src/test/resources/examples/helloworld.json b/api/src/test/resources/examples/helloworld.json deleted file mode 100644 index c1864a6d..00000000 --- a/api/src/test/resources/examples/helloworld.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "helloworld", - "version": "1.0", - "name": "Hello World Workflow", - "description": "Inject Hello World", - "start": "Hello State", - "states":[ - { - "name":"Hello State", - "type":"inject", - "data": { - "result": "Hello World!" - }, - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/examples/helloworld.yml b/api/src/test/resources/examples/helloworld.yml deleted file mode 100644 index 0d537603..00000000 --- a/api/src/test/resources/examples/helloworld.yml +++ /dev/null @@ -1,11 +0,0 @@ -id: helloworld -version: '1.0' -name: Hello World Workflow -description: Inject Hello World -start: Hello State -states: - - name: Hello State - type: inject - data: - result: Hello World! - end: true \ No newline at end of file diff --git a/api/src/test/resources/examples/jobmonitoring.json b/api/src/test/resources/examples/jobmonitoring.json deleted file mode 100644 index 1eeba8a0..00000000 --- a/api/src/test/resources/examples/jobmonitoring.json +++ /dev/null @@ -1,138 +0,0 @@ -{ - "id": "jobmonitoring", - "version": "1.0", - "name": "Job Monitoring", - "description": "Monitor finished execution of a submitted job", - "start": "SubmitJob", - "functions": [ - { - "name": "submitJob", - "operation": "http://myapis.org/monitorapi.json#doSubmit" - }, - { - "name": "checkJobStatus", - "operation": "http://myapis.org/monitorapi.json#checkStatus" - }, - { - "name": "reportJobSuceeded", - "operation": "http://myapis.org/monitorapi.json#reportSucceeded" - }, - { - "name": "reportJobFailed", - "operation": "http://myapis.org/monitorapi.json#reportFailure" - } - ], - "states":[ - { - "name":"SubmitJob", - "type":"operation", - "actionMode":"sequential", - "actions":[ - { - "functionRef": { - "refName": "submitJob", - "arguments": { - "name": "${ .job.name }" - } - }, - "actionDataFilter": { - "results": "${ .jobuid }" - } - } - ], - "onErrors": [ - { - "error": "*", - "transition": "SubmitError" - } - ], - "stateDataFilter": { - "output": "${ .jobuid }" - }, - "transition": "WaitForCompletion" - }, - { - "name": "SubmitError", - "type": "subflow", - "workflowId": "handleJobSubmissionErrorWorkflow", - "end": true - }, - { - "name": "WaitForCompletion", - "type": "delay", - "timeDelay": "PT5S", - "transition": "GetJobStatus" - }, - { - "name":"GetJobStatus", - "type":"operation", - "actionMode":"sequential", - "actions":[ - { - "functionRef": { - "refName": "checkJobStatus", - "arguments": { - "name": "${ .jobuid }" - } - }, - "actionDataFilter": { - "results": "${ .jobstatus }" - } - } - ], - "stateDataFilter": { - "output": "${ .jobstatus }" - }, - "transition": "DetermineCompletion" - }, - { - "name":"DetermineCompletion", - "type":"switch", - "dataConditions": [ - { - "condition": "${ .jobStatus == \"SUCCEEDED\" }", - "transition": "JobSucceeded" - }, - { - "condition": "${ .jobStatus == \"FAILED\" }", - "transition": "JobFailed" - } - ], - "default": { - "transition": "WaitForCompletion" - } - }, - { - "name":"JobSucceeded", - "type":"operation", - "actionMode":"sequential", - "actions":[ - { - "functionRef": { - "refName": "reportJobSuceeded", - "arguments": { - "name": "${ .jobuid }" - } - } - } - ], - "end": true - }, - { - "name":"JobFailed", - "type":"operation", - "actionMode":"sequential", - "actions":[ - { - "functionRef": { - "refName": "reportJobFailed", - "arguments": { - "name": "${ .jobuid }" - } - } - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/examples/jobmonitoring.yml b/api/src/test/resources/examples/jobmonitoring.yml deleted file mode 100644 index 5f30b08e..00000000 --- a/api/src/test/resources/examples/jobmonitoring.yml +++ /dev/null @@ -1,79 +0,0 @@ -id: jobmonitoring -version: '1.0' -name: Job Monitoring -description: Monitor finished execution of a submitted job -start: SubmitJob -functions: - - name: submitJob - operation: http://myapis.org/monitorapi.json#doSubmit - - name: checkJobStatus - operation: http://myapis.org/monitorapi.json#checkStatus - - name: reportJobSuceeded - operation: http://myapis.org/monitorapi.json#reportSucceeded - - name: reportJobFailed - operation: http://myapis.org/monitorapi.json#reportFailure -states: - - name: SubmitJob - type: operation - actionMode: sequential - actions: - - functionRef: - refName: submitJob - arguments: - name: "${ .job.name }" - actionDataFilter: - results: "${ .jobuid }" - onErrors: - - error: "*" - transition: SubmitError - stateDataFilter: - output: "${ .jobuid }" - transition: WaitForCompletion - - name: SubmitError - type: subflow - workflowId: handleJobSubmissionErrorWorkflow - end: true - - name: WaitForCompletion - type: delay - timeDelay: PT5S - transition: GetJobStatus - - name: GetJobStatus - type: operation - actionMode: sequential - actions: - - functionRef: - refName: checkJobStatus - arguments: - name: "${ .jobuid }" - actionDataFilter: - results: "${ .jobstatus }" - stateDataFilter: - output: "${ .jobstatus }" - transition: DetermineCompletion - - name: DetermineCompletion - type: switch - dataConditions: - - condition: "${ .jobstatus == \"SUCCEEDED\" }" - transition: JobSucceeded - - condition: "${ .jobstatus == \"FAILED\" }" - transition: JobFailed - default: - transition: WaitForCompletion - - name: JobSucceeded - type: operation - actionMode: sequential - actions: - - functionRef: - refName: reportJobSuceeded - arguments: - name: "${ .jobuid }" - end: true - - name: JobFailed - type: operation - actionMode: sequential - actions: - - functionRef: - refName: reportJobFailed - arguments: - name: "${ .jobuid }" - end: true \ No newline at end of file diff --git a/api/src/test/resources/examples/monitorpatient.json b/api/src/test/resources/examples/monitorpatient.json deleted file mode 100644 index ab216b60..00000000 --- a/api/src/test/resources/examples/monitorpatient.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "id": "patientVitalsWorkflow", - "name": "Monitor Patient Vitals", - "version": "1.0", - "start": "MonitorVitals", - "events": [ - { - "name": "HighBodyTemperature", - "type": "org.monitor.highBodyTemp", - "source": "monitoringSource", - "correlation": [ - { - "contextAttributeName": "patientId" - } - ] - }, - { - "name": "HighBloodPressure", - "type": "org.monitor.highBloodPressure", - "source": "monitoringSource", - "correlation": [ - { - "contextAttributeName": "patientId" - } - ] - }, - { - "name": "HighRespirationRate", - "type": "org.monitor.highRespirationRate", - "source": "monitoringSource", - "correlation": [ - { - "contextAttributeName": "patientId" - } - ] - } - ], - "functions": [ - { - "name": "callPulmonologist", - "operation": "http://myapis.org/patientapis.json#callPulmonologist" - }, - { - "name": "sendTylenolOrder", - "operation": "http://myapis.org/patientapis.json#tylenolOrder" - }, - { - "name": "callNurse", - "operation": "http://myapis.org/patientapis.json#callNurse" - } - ], - "states": [ - { - "name": "MonitorVitals", - "type": "event", - "exclusive": true, - "onEvents": [{ - "eventRefs": ["HighBodyTemperature"], - "actions": [{ - "functionRef": { - "refName": "sendTylenolOrder", - "arguments": { - "patientid": "${ .patientId }" - } - } - }] - }, - { - "eventRefs": ["HighBloodPressure"], - "actions": [{ - "functionRef": { - "refName": "callNurse", - "arguments": { - "patientid": "${ .patientId }" - } - } - }] - }, - { - "eventRefs": ["HighRespirationRate"], - "actions": [{ - "functionRef": { - "refName": "callPulmonologist", - "arguments": { - "patientid": "${ .patientId }" - } - } - }] - } - ], - "end": { - "terminate": true - } - }] -} \ No newline at end of file diff --git a/api/src/test/resources/examples/monitorpatient.yml b/api/src/test/resources/examples/monitorpatient.yml deleted file mode 100644 index 0db0aff8..00000000 --- a/api/src/test/resources/examples/monitorpatient.yml +++ /dev/null @@ -1,55 +0,0 @@ -id: patientVitalsWorkflow -name: Monitor Patient Vitals -version: '1.0' -start: Monitor Vitals -events: - - name: HighBodyTemperature - type: org.monitor.highBodyTemp - source: monitoringSource - correlation: - - contextAttributeName: patientId - - name: HighBloodPressure - type: org.monitor.highBloodPressure - source: monitoringSource - correlation: - - contextAttributeName: patientId - - name: HighRespirationRate - type: org.monitor.highRespirationRate - source: monitoringSource - correlation: - - contextAttributeName: patientId -functions: - - name: callPulmonologist - operation: http://myapis.org/patientapis.json#callPulmonologist - - name: sendTylenolOrder - operation: http://myapis.org/patientapis.json#tylenolOrder - - name: callNurse - operation: http://myapis.org/patientapis.json#callNurse -states: - - name: MonitorVitals - type: event - exclusive: true - onEvents: - - eventRefs: - - HighBodyTemperature - actions: - - functionRef: - refName: sendTylenolOrder - arguments: - patientid: "${ .patientId }" - - eventRefs: - - HighBloodPressure - actions: - - functionRef: - refName: callNurse - arguments: - patientid: "${ .patientId }" - - eventRefs: - - HighRespirationRate - actions: - - functionRef: - refName: callPulmonologist - arguments: - patientid: "${ .patientId }" - end: - terminate: true \ No newline at end of file diff --git a/api/src/test/resources/examples/parallel.json b/api/src/test/resources/examples/parallel.json deleted file mode 100644 index cb50c8fc..00000000 --- a/api/src/test/resources/examples/parallel.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "id": "parallelexec", - "version": "1.0", - "name": "Parallel Execution Workflow", - "description": "Executes two branches in parallel", - "start": "ParallelExec", - "states":[ - { - "name": "ParallelExec", - "type": "parallel", - "completionType": "and", - "branches": [ - { - "name": "ShortDelayBranch", - "workflowId": "shortdelayworkflowid" - }, - { - "name": "LongDelayBranch", - "workflowId": "longdelayworkflowid" - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/examples/parallel.yml b/api/src/test/resources/examples/parallel.yml deleted file mode 100644 index 49536973..00000000 --- a/api/src/test/resources/examples/parallel.yml +++ /dev/null @@ -1,15 +0,0 @@ -id: parallelexec -version: '1.0' -name: Parallel Execution Workflow -description: Executes two branches in parallel -start: ParallelExec -states: - - name: ParallelExec - type: parallel - completionType: and - branches: - - name: ShortDelayBranch - workflowId: shortdelayworkflowid - - name: LongDelayBranch - workflowId: longdelayworkflowid - end: true \ No newline at end of file diff --git a/api/src/test/resources/examples/periodicinboxcheck.json b/api/src/test/resources/examples/periodicinboxcheck.json deleted file mode 100644 index 5e20c30e..00000000 --- a/api/src/test/resources/examples/periodicinboxcheck.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "id": "checkInbox", - "name": "Check Inbox Workflow", - "description": "Periodically Check Inbox", - "start": { - "stateName": "CheckInbox", - "schedule": { - "cron": "0 0/15 * * * ?" - } - }, - "version": "1.0", - "functions": [ - { - "name": "checkInboxFunction", - "operation": "http://myapis.org/inboxapi.json#checkNewMessages" - }, - { - "name": "sendTextFunction", - "operation": "http://myapis.org/inboxapi.json#sendText" - } - ], - "states": [ - { - "name": "CheckInbox", - "type": "operation", - "actionMode": "sequential", - "actions": [ - { - "functionRef": "checkInboxFunction" - } - ], - "transition": "SendTextForHighPriority" - }, - { - "name": "SendTextForHighPriority", - "type": "foreach", - "inputCollection": "${ .messages }", - "iterationParam": "singlemessage", - "actions": [ - { - "functionRef": { - "refName": "sendTextFunction", - "arguments": { - "message": "${ .singlemessage }" - } - } - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/examples/periodicinboxcheck.yml b/api/src/test/resources/examples/periodicinboxcheck.yml deleted file mode 100644 index 1d7de0bb..00000000 --- a/api/src/test/resources/examples/periodicinboxcheck.yml +++ /dev/null @@ -1,30 +0,0 @@ -id: checkInbox -name: Check Inbox Workflow -description: Periodically Check Inbox -start: - stateName: CheckInbox - schedule: - cron: 0 0/15 * * * ? -version: '1.0' -functions: - - name: checkInboxFunction - operation: http://myapis.org/inboxapi.json#checkNewMessages - - name: sendTextFunction - operation: http://myapis.org/inboxapi.json#sendText -states: - - name: CheckInbox - type: operation - actionMode: sequential - actions: - - functionRef: checkInboxFunction - transition: SendTextForHighPriority - - name: SendTextForHighPriority - type: foreach - inputCollection: "${ .messages }" - iterationParam: singlemessage - actions: - - functionRef: - refName: sendTextFunction - arguments: - message: "${ .singlemessage }" - end: true \ No newline at end of file diff --git a/api/src/test/resources/examples/provisionorder.json b/api/src/test/resources/examples/provisionorder.json deleted file mode 100644 index 1fb59102..00000000 --- a/api/src/test/resources/examples/provisionorder.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "id": "provisionorders", - "version": "1.0", - "name": "Provision Orders", - "description": "Provision Orders and handle errors thrown", - "start": "ProvisionOrder", - "functions": [ - { - "name": "provisionOrderFunction", - "operation": "http://myapis.org/provisioningapi.json#doProvision" - } - ], - "states":[ - { - "name":"ProvisionOrder", - "type":"operation", - "actionMode":"sequential", - "actions":[ - { - "functionRef": { - "refName": "provisionOrderFunction", - "arguments": { - "order": "${ .order }" - } - } - } - ], - "stateDataFilter": { - "output": "${ .exceptions }" - }, - "transition": "ApplyOrder", - "onErrors": [ - { - "error": "Missing order id", - "transition": "MissingId" - }, - { - "error": "Missing order item", - "transition": "MissingItem" - }, - { - "error": "Missing order quantity", - "transition": "MissingQuantity" - } - ] - }, - { - "name": "MissingId", - "type": "subflow", - "workflowId": "handleMissingIdExceptionWorkflow", - "end": true - }, - { - "name": "MissingItem", - "type": "subflow", - "workflowId": "handleMissingItemExceptionWorkflow", - "end": true - }, - { - "name": "MissingQuantity", - "type": "subflow", - "workflowId": "handleMissingQuantityExceptionWorkflow", - "end": true - }, - { - "name": "ApplyOrder", - "type": "subflow", - "workflowId": "applyOrderWorkflowId", - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/examples/provisionorder.yml b/api/src/test/resources/examples/provisionorder.yml deleted file mode 100644 index b8eff2be..00000000 --- a/api/src/test/resources/examples/provisionorder.yml +++ /dev/null @@ -1,43 +0,0 @@ -id: provisionorders -version: '1.0' -name: Provision Orders -description: Provision Orders and handle errors thrown -start: ProvisionOrder -functions: - - name: provisionOrderFunction - operation: http://myapis.org/provisioningapi.json#doProvision -states: - - name: ProvisionOrder - type: operation - actionMode: sequential - actions: - - functionRef: - refName: provisionOrderFunction - arguments: - order: "${ .order }" - stateDataFilter: - output: "${ .exceptions }" - transition: ApplyOrder - onErrors: - - error: Missing order id - transition: MissingId - - error: Missing order item - transition: MissingItem - - error: Missing order quantity - transition: MissingQuantity - - name: MissingId - type: subflow - workflowId: handleMissingIdExceptionWorkflow - end: true - - name: MissingItem - type: subflow - workflowId: handleMissingItemExceptionWorkflow - end: true - - name: MissingQuantity - type: subflow - workflowId: handleMissingQuantityExceptionWorkflow - end: true - - name: ApplyOrder - type: subflow - workflowId: applyOrderWorkflowId - end: true \ No newline at end of file diff --git a/api/src/test/resources/examples/roomreadings.json b/api/src/test/resources/examples/roomreadings.json deleted file mode 100644 index aab4f25d..00000000 --- a/api/src/test/resources/examples/roomreadings.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "id": "roomreadings", - "name": "Room Temp and Humidity Workflow", - "version": "1.0", - "start": "ConsumeReading", - "execTimeout": { - "duration": "PT1H", - "runBefore": "GenerateReport" - }, - "keepActive": true, - "states": [ - { - "name": "ConsumeReading", - "type": "event", - "onEvents": [ - { - "eventRefs": ["TemperatureEvent", "HumidityEvent"], - "actions": [ - { - "functionRef": { - "refName": "LogReading" - } - } - ], - "eventDataFilter": { - "data": "${ .readings }" - } - } - ], - "end": true - }, - { - "name": "GenerateReport", - "type": "operation", - "actions": [ - { - "functionRef": { - "refName": "ProduceReport", - "arguments": { - "data": "${ .readings }" - } - } - } - ], - "end": { - "terminate": true - } - } - ], - "events": [ - { - "name": "TemperatureEvent", - "type": "my.home.sensors", - "source": "/home/rooms/+", - "correlation": [ - { - "contextAttributeName": "roomId" - } - ] - }, - { - "name": "HumidityEvent", - "type": "my.home.sensors", - "source": "/home/rooms/+", - "correlation": [ - { - "contextAttributeName": "roomId" - } - ] - } - ], - "functions": [ - { - "name": "LogReading", - "operation": "http.myorg.io/ordersservices.json#logreading" - }, - { - "name": "ProduceReport", - "operation": "http.myorg.io/ordersservices.json#produceReport" - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/examples/roomreadings.yml b/api/src/test/resources/examples/roomreadings.yml deleted file mode 100644 index cfbf2e82..00000000 --- a/api/src/test/resources/examples/roomreadings.yml +++ /dev/null @@ -1,46 +0,0 @@ -id: roomreadings -name: Room Temp and Humidity Workflow -version: '1.0' -start: ConsumeReading -execTimeout: - duration: PT1H - runBefore: GenerateReport -keepActive: true -states: - - name: ConsumeReading - type: event - onEvents: - - eventRefs: - - TemperatureEvent - - HumidityEvent - actions: - - functionRef: - refName: LogReading - eventDataFilter: - data: "${ .readings }" - end: true - - name: GenerateReport - type: operation - actions: - - functionRef: - refName: ProduceReport - arguments: - data: "${ .readings }" - end: - terminate: true -events: - - name: TemperatureEvent - type: my.home.sensors - source: "/home/rooms/+" - correlation: - - contextAttributeName: roomId - - name: HumidityEvent - type: my.home.sensors - source: "/home/rooms/+" - correlation: - - contextAttributeName: roomId -functions: - - name: LogReading - operation: http.myorg.io/ordersservices.json#logreading - - name: ProduceReport - operation: http.myorg.io/ordersservices.json#produceReport \ No newline at end of file diff --git a/api/src/test/resources/examples/sendcloudevent.json b/api/src/test/resources/examples/sendcloudevent.json deleted file mode 100644 index 5b94fc11..00000000 --- a/api/src/test/resources/examples/sendcloudevent.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "id": "sendcloudeventonprovision", - "version": "1.0", - "name": "Send CloudEvent on provision completion", - "start": "ProvisionOrdersState", - "events": [ - { - "name": "provisioningCompleteEvent", - "type": "provisionCompleteType", - "kind": "produced" - } - ], - "functions": [ - { - "name": "provisionOrderFunction", - "operation": "http://myapis.org/provisioning.json#doProvision" - } - ], - "states": [ - { - "name": "ProvisionOrdersState", - "type": "foreach", - "inputCollection": "${ .orders }", - "iterationParam": "singleorder", - "outputCollection": "${ .provisionedOrders }", - "actions": [ - { - "functionRef": { - "refName": "provisionOrderFunction", - "arguments": { - "order": "${ .singleorder }" - } - } - } - ], - "end": { - "produceEvents": [{ - "eventRef": "provisioningCompleteEvent", - "data": "${ .provisionedOrders }" - }] - } - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/examples/sendcloudevent.yml b/api/src/test/resources/examples/sendcloudevent.yml deleted file mode 100644 index 1359fb10..00000000 --- a/api/src/test/resources/examples/sendcloudevent.yml +++ /dev/null @@ -1,26 +0,0 @@ -id: sendcloudeventonprovision -version: '1.0' -name: Send CloudEvent on provision completion -start: ProvisionOrdersState -events: - - name: provisioningCompleteEvent - type: provisionCompleteType - kind: produced -functions: - - name: provisionOrderFunction - operation: http://myapis.org/provisioning.json#doProvision -states: - - name: ProvisionOrdersState - type: foreach - inputCollection: "${ .orders }" - iterationParam: singleorder - outputCollection: "${ .provisionedOrders }" - actions: - - functionRef: - refName: provisionOrderFunction - arguments: - order: "${ .singleorder }" - end: - produceEvents: - - eventRef: provisioningCompleteEvent - data: "${ .provisionedOrders }" \ No newline at end of file diff --git a/api/src/test/resources/examples/solvemathproblems.json b/api/src/test/resources/examples/solvemathproblems.json deleted file mode 100644 index 25e00e33..00000000 --- a/api/src/test/resources/examples/solvemathproblems.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "id": "solvemathproblems", - "version": "1.0", - "name": "Solve Math Problems Workflow", - "description": "Solve math problems", - "start": "Solve", - "functions": [ - { - "name": "solveMathExpressionFunction", - "operation": "http://myapis.org/mapthapis.json#solveExpression" - } - ], - "states":[ - { - "name":"Solve", - "type":"foreach", - "inputCollection": "${ .expressions }", - "iterationParam": "singleexpression", - "outputCollection": "${ .results }", - "actions":[ - { - "functionRef": { - "refName": "solveMathExpressionFunction", - "arguments": { - "expression": "${ .singleexpression }" - } - } - } - ], - "stateDataFilter": { - "output": "${ .results }" - }, - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/examples/solvemathproblems.yml b/api/src/test/resources/examples/solvemathproblems.yml deleted file mode 100644 index b7bd1e27..00000000 --- a/api/src/test/resources/examples/solvemathproblems.yml +++ /dev/null @@ -1,22 +0,0 @@ -id: solvemathproblems -version: '1.0' -name: Solve Math Problems Workflow -description: Solve math problems -start: Solve -functions: - - name: solveMathExpressionFunction - operation: http://myapis.org/mapthapis.json#solveExpression -states: - - name: Solve - type: foreach - inputCollection: "${ .expressions }" - iterationParam: singleexpression - outputCollection: "${ .results }" - actions: - - functionRef: - refName: solveMathExpressionFunction - arguments: - expression: "${ .singleexpression }" - stateDataFilter: - output: "${ .results }" - end: true \ No newline at end of file diff --git a/api/src/test/resources/examples/vetappointmentservice.json b/api/src/test/resources/examples/vetappointmentservice.json deleted file mode 100644 index 230e0f73..00000000 --- a/api/src/test/resources/examples/vetappointmentservice.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "id": "VetAppointmentWorkflow", - "name": "Vet Appointment Workflow", - "description": "Vet service call via events", - "version": "1.0", - "start": "MakeVetAppointmentState", - "events": [ - { - "name": "MakeVetAppointment", - "source": "VetServiceSoure", - "kind": "produced" - }, - { - "name": "VetAppointmentInfo", - "source": "VetServiceSource", - "kind": "consumed" - } - ], - "states": [ - { - "name": "MakeVetAppointmentState", - "type": "operation", - "actions": [ - { - "name": "MakeAppointmentAction", - "eventRef": { - "triggerEventRef": "MakeVetAppointment", - "data": "${ .patientInfo }", - "resultEventRef": "VetAppointmentInfo" - }, - "actionDataFilter": { - "results": "${ .appointmentInfo }" - }, - "timeout": "PT15M" - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/examples/vetappointmentservice.yml b/api/src/test/resources/examples/vetappointmentservice.yml deleted file mode 100644 index d976067e..00000000 --- a/api/src/test/resources/examples/vetappointmentservice.yml +++ /dev/null @@ -1,25 +0,0 @@ -id: VetAppointmentWorkflow -name: Vet Appointment Workflow -description: Vet service call via events -version: '1.0' -start: MakeVetAppointmentState -events: - - name: MakeVetAppointment - source: VetServiceSoure - kind: produced - - name: VetAppointmentInfo - source: VetServiceSource - kind: consumed -states: - - name: MakeVetAppointmentState - type: operation - actions: - - name: MakeAppointmentAction - eventRef: - triggerEventRef: MakeVetAppointment - data: "${ .patientInfo }" - resultEventRef: VetAppointmentInfo - actionDataFilter: - results: "${ .appointmentInfo }" - timeout: PT15M - end: true \ No newline at end of file diff --git a/api/src/test/resources/features/applicantrequest.json b/api/src/test/resources/features/applicantrequest.json deleted file mode 100644 index 653667ef..00000000 --- a/api/src/test/resources/features/applicantrequest.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "id": "applicantrequest", - "version": "1.0", - "name": "Applicant Request Decision Workflow", - "start": "CheckApplication", - "description": "Determine if applicant request is valid", - "functions": "features/applicantrequestfunctions.json", - "retries": "features/applicantrequestretries.json", - "states":[ - { - "name":"CheckApplication", - "type":"switch", - "dataConditions": [ - { - "condition": "${ .applicants[?(@.age >= 18)] }", - "transition": "StartApplication" - }, - { - "condition": "${ .applicants[?(@.age < 18)] }", - "transition": "RejectApplication" - } - ], - "default": { - "transition": "RejectApplication" - } - }, - { - "name": "StartApplication", - "type": "subflow", - "workflowId": "startApplicationWorkflowId", - "end": true - }, - { - "name":"RejectApplication", - "type":"operation", - "actionMode":"sequential", - "actions":[ - { - "functionRef": { - "refName": "sendRejectionEmailFunction", - "arguments": { - "applicant": "${ .applicant }" - } - } - } - ], - "onErrors": [ - { - "error": "TimeoutError", - "code": "500", - "retryRef": "TimeoutRetryStrategy", - "end": true - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/features/applicantrequest.yml b/api/src/test/resources/features/applicantrequest.yml deleted file mode 100644 index 35f2b59f..00000000 --- a/api/src/test/resources/features/applicantrequest.yml +++ /dev/null @@ -1,35 +0,0 @@ -id: applicantrequest -version: '1.0' -start: CheckApplication -name: Applicant Request Decision Workflow -description: Determine if applicant request is valid -functions: features/applicantrequestfunctions.yml -retries: features/applicantrequestretries.yml -states: - - name: CheckApplication - type: switch - dataConditions: - - condition: "${ .applicants[?(@.age >= 18)] }" - transition: StartApplication - - condition: "${ .applicants[?(@.age < 18)] }" - transition: RejectApplication - default: - transition: RejectApplication - - name: StartApplication - type: subflow - workflowId: startApplicationWorkflowId - end: true - - name: RejectApplication - type: operation - actionMode: sequential - actions: - - functionRef: - refName: sendRejectionEmailFunction - arguments: - applicant: "${ .applicant }" - onErrors: - - error: TimeoutError - code: '500' - retryRef: TimeoutRetryStrategy - end: true - end: true diff --git a/api/src/test/resources/features/applicantrequestfunctions.json b/api/src/test/resources/features/applicantrequestfunctions.json deleted file mode 100644 index bafc861b..00000000 --- a/api/src/test/resources/features/applicantrequestfunctions.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "functions": [ - { - "name": "sendRejectionEmailFunction", - "operation": "http://myapis.org/application.json#emailRejection" - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/features/applicantrequestfunctions.yml b/api/src/test/resources/features/applicantrequestfunctions.yml deleted file mode 100644 index a1e90f93..00000000 --- a/api/src/test/resources/features/applicantrequestfunctions.yml +++ /dev/null @@ -1,3 +0,0 @@ -functions: - - name: sendRejectionEmailFunction - operation: http://myapis.org/application.json#emailRejection diff --git a/api/src/test/resources/features/applicantrequestretries.json b/api/src/test/resources/features/applicantrequestretries.json deleted file mode 100644 index 40f83b55..00000000 --- a/api/src/test/resources/features/applicantrequestretries.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "retries": [ - { - "name": "TimeoutRetryStrategy", - "delay": "PT1M", - "maxAttempts": "5" - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/features/applicantrequestretries.yml b/api/src/test/resources/features/applicantrequestretries.yml deleted file mode 100644 index fa4c810d..00000000 --- a/api/src/test/resources/features/applicantrequestretries.yml +++ /dev/null @@ -1,4 +0,0 @@ -retries: - - name: TimeoutRetryStrategy - delay: PT1M - maxAttempts: '5' diff --git a/api/src/test/resources/features/authentication-bearer-uri-format.yaml b/api/src/test/resources/features/authentication-bearer-uri-format.yaml new file mode 100644 index 00000000..b0019fbb --- /dev/null +++ b/api/src/test/resources/features/authentication-bearer-uri-format.yaml @@ -0,0 +1,15 @@ +document: + dsl: '1.0.0-alpha5' + namespace: examples + name: bearer-auth + version: '0.1.0' +do: + - getPet: + call: http + with: + method: get + endpoint: + uri: https://petstore.swagger.io/v2/pet/{petId} + authentication: + bearer: + token: ${ .token } diff --git a/api/src/test/resources/features/authentication-bearer.yaml b/api/src/test/resources/features/authentication-bearer.yaml new file mode 100644 index 00000000..f0c42741 --- /dev/null +++ b/api/src/test/resources/features/authentication-bearer.yaml @@ -0,0 +1,15 @@ +document: + dsl: '1.0.0-alpha5' + namespace: examples + name: bearer-auth-uri-format + version: '0.1.0' +do: + - getPet: + call: http + with: + method: get + endpoint: + uri: https://petstore.swagger.io/v2/pet/1 + authentication: + bearer: + token: ${ .token } \ No newline at end of file diff --git a/api/src/test/resources/features/authentication-oauth2-secret.yaml b/api/src/test/resources/features/authentication-oauth2-secret.yaml new file mode 100644 index 00000000..635076ab --- /dev/null +++ b/api/src/test/resources/features/authentication-oauth2-secret.yaml @@ -0,0 +1,18 @@ +document: + dsl: 1.0.0-alpha1 + namespace: examples + name: oauth2-authentication + version: 1.0.0-alpha1 +use: + secrets: + - mySecret +do: + - getPet: + call: http + with: + method: get + endpoint: + uri: https://petstore.swagger.io/v2/pet/{petId} + authentication: + oauth2: + use: mySecret \ No newline at end of file diff --git a/api/src/test/resources/features/authentication-oauth2.yaml b/api/src/test/resources/features/authentication-oauth2.yaml new file mode 100644 index 00000000..625a1e2c --- /dev/null +++ b/api/src/test/resources/features/authentication-oauth2.yaml @@ -0,0 +1,22 @@ +document: + dsl: '1.0.0-alpha5' + namespace: examples + name: oauth2-authentication + version: '0.1.0' +do: + - getPet: + call: http + with: + method: get + endpoint: + uri: https://petstore.swagger.io/v2/pet/{petId} + authentication: + oauth2: + authority: http://keycloak/realms/fake-authority + endpoints: #optional + token: /auth/token #defaults to /oauth2/token + introspection: /auth/introspect #defaults to /oauth2/introspect + grant: client_credentials + client: + id: workflow-runtime-id + secret: workflow-runtime-secret \ No newline at end of file diff --git a/api/src/test/resources/features/authentication-oidc-secret.yaml b/api/src/test/resources/features/authentication-oidc-secret.yaml new file mode 100644 index 00000000..19c387c1 --- /dev/null +++ b/api/src/test/resources/features/authentication-oidc-secret.yaml @@ -0,0 +1,18 @@ +document: + dsl: 1.0.0-alpha1 + namespace: examples + name: oidc-authentication + version: 1.0.0-alpha1 +use: + secrets: + - mySecret +do: + - getPet: + call: http + with: + method: get + endpoint: + uri: https://petstore.swagger.io/v2/pet/{petId} + authentication: + oidc: + use: mySecret \ No newline at end of file diff --git a/api/src/test/resources/features/authentication-oidc.yaml b/api/src/test/resources/features/authentication-oidc.yaml new file mode 100644 index 00000000..18aec74d --- /dev/null +++ b/api/src/test/resources/features/authentication-oidc.yaml @@ -0,0 +1,19 @@ +document: + dsl: '1.0.0-alpha5' + namespace: examples + name: oidc-authentication + version: '0.1.0' +do: + - getPet: + call: http + with: + method: get + endpoint: + uri: https://petstore.swagger.io/v2/pet/{petId} + authentication: + oidc: + authority: http://keycloak/realms/fake-authority #endpoints are resolved using the OIDC configuration located at '/.well-known/openid-configuration' + grant: client_credentials + client: + id: workflow-runtime-id + secret: workflow-runtime-secret \ No newline at end of file diff --git a/api/src/test/resources/features/authentication-reusable.yaml b/api/src/test/resources/features/authentication-reusable.yaml new file mode 100644 index 00000000..a5da803d --- /dev/null +++ b/api/src/test/resources/features/authentication-reusable.yaml @@ -0,0 +1,19 @@ +document: + dsl: '1.0.0-alpha5' + namespace: examples + name: bearer-auth + version: '0.1.0' +use: + authentications: + petStoreAuth: + bearer: + token: ${ .token } +do: + - getPet: + call: http + with: + method: get + endpoint: + uri: https://petstore.swagger.io/v2/pet/{petId} + authentication: + use: petStoreAuth diff --git a/api/src/test/resources/features/call-http-query-parameters.yaml b/api/src/test/resources/features/call-http-query-parameters.yaml new file mode 100644 index 00000000..95934315 --- /dev/null +++ b/api/src/test/resources/features/call-http-query-parameters.yaml @@ -0,0 +1,24 @@ +document: + dsl: 1.0.0-alpha2 + namespace: examples + name: http-query-params + version: 1.0.0-alpha2 +input: + schema: + format: json + document: + type: object + required: + - searchQuery + properties: + searchQuery: + type: string +do: + - searchStarWarsCharacters: + call: http + with: + method: get + endpoint: https://swapi.dev/api/people/ + query: + search: ${.searchQuery} + diff --git a/api/src/test/resources/features/callCustomFunction.yaml b/api/src/test/resources/features/callCustomFunction.yaml new file mode 100644 index 00000000..fbb636b4 --- /dev/null +++ b/api/src/test/resources/features/callCustomFunction.yaml @@ -0,0 +1,25 @@ +document: + dsl: '1.0.0-alpha5' + namespace: samples + name: call-custom-function-inline + version: '0.1.0' +use: + functions: + getPetById: + input: + schema: + document: + type: object + properties: + petId: + type: string + required: [ petId ] + call: http + with: + method: get + endpoint: https://petstore.swagger.io/v2/pet/{petId} +do: + - getPet: + call: getPetById + with: + petId: 69 \ No newline at end of file diff --git a/api/src/test/resources/features/callFunction.yaml b/api/src/test/resources/features/callFunction.yaml new file mode 100644 index 00000000..95a3a987 --- /dev/null +++ b/api/src/test/resources/features/callFunction.yaml @@ -0,0 +1,19 @@ +document: + dsl: 1.0.0-alpha1 + namespace: default + name: http-call-with-response-output + version: 1.0.0 + +use: + functions: + getPet: + call: http + with: + method: get + endpoint: + uri: https://petstore.swagger.io/v2/pet/{petId} + output: response + +do: + - getPetFunctionCall: + call: getPet \ No newline at end of file diff --git a/api/src/test/resources/features/callHttp.yaml b/api/src/test/resources/features/callHttp.yaml new file mode 100644 index 00000000..4022e38a --- /dev/null +++ b/api/src/test/resources/features/callHttp.yaml @@ -0,0 +1,13 @@ +document: + dsl: 1.0.0-alpha1 + namespace: default + name: http-call-with-response-output + version: 1.0.0 +do: + - getPet: + call: http + with: + method: get + endpoint: + uri: https://petstore.swagger.io/v2/pet/{petId} + output: response \ No newline at end of file diff --git a/api/src/test/resources/features/callOpenAPI.yaml b/api/src/test/resources/features/callOpenAPI.yaml new file mode 100644 index 00000000..82843c5d --- /dev/null +++ b/api/src/test/resources/features/callOpenAPI.yaml @@ -0,0 +1,16 @@ +document: + dsl: 1.0.0-alpha1 + namespace: default + name: openapi-call-with-content-output + version: 1.0.0 +do: + - findPet: + call: openapi + with: + document: + endpoint: "https://petstore.swagger.io/v2/swagger.json" + operationId: findPetsByStatus + parameters: + status: ${ .status } + output: + as: . | length \ No newline at end of file diff --git a/api/src/test/resources/features/checkcarvitals.json b/api/src/test/resources/features/checkcarvitals.json deleted file mode 100644 index 063d51a2..00000000 --- a/api/src/test/resources/features/checkcarvitals.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "id": "checkcarvitals", - "name": "Check Car Vitals Workflow", - "version": "1.0", - "start": "WhenCarIsOn", - "states": [ - { - "name": "WhenCarIsOn", - "type": "event", - "onEvents": [ - { - "eventRefs": ["CarTurnedOnEvent"] - } - ], - "transition": "DoCarVitalsChecks" - }, - { - "name": "DoCarVitalsChecks", - "type": "subflow", - "workflowId": "vitalscheck", - "repeat": { - "max": 10, - "continueOnError": true, - "stopOnEvents": ["CarTurnedOffEvent"] - }, - "end": true - } - ], - "events": [ - { - "name": "CarTurnedOnEvent", - "type": "car.events", - "source": "my/car/start" - }, - { - "name": "CarTurnedOffEvent", - "type": "car.events", - "source": "my/car/start" - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/features/checkcarvitals.yml b/api/src/test/resources/features/checkcarvitals.yml deleted file mode 100644 index cc1bfb87..00000000 --- a/api/src/test/resources/features/checkcarvitals.yml +++ /dev/null @@ -1,28 +0,0 @@ ---- -id: checkcarvitals -name: Check Car Vitals Workflow -version: '1.0' -start: WhenCarIsOn -states: - - name: WhenCarIsOn - type: event - onEvents: - - eventRefs: - - CarTurnedOnEvent - transition: DoCarVitalsChecks - - name: DoCarVitalsChecks - type: subflow - workflowId: vitalscheck - repeat: - max: 10 - continueOnError: true - stopOnEvents: - - CarTurnedOffEvent - end: true -events: - - name: CarTurnedOnEvent - type: car.events - source: my/car/start - - name: CarTurnedOffEvent - type: car.events - source: my/car/start diff --git a/api/src/test/resources/features/compensationworkflow.json b/api/src/test/resources/features/compensationworkflow.json deleted file mode 100644 index 7376cd28..00000000 --- a/api/src/test/resources/features/compensationworkflow.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "id": "CompensationWorkflow", - "name": "Compensation Workflow", - "version": "1.0", - "states": [ - { - "name": "NewItemPurchase", - "type": "event", - "onEvents": [ - { - "eventRefs": [ - "NewPurchase" - ], - "actions": [ - { - "functionRef": { - "refName": "DebitCustomerFunction", - "arguments": { - "customerid": "${ .purchase.customerid }", - "amount": "${ .purchase.amount }" - } - } - }, - { - "functionRef": { - "refName": "SendPurchaseConfirmationEmailFunction", - "arguments": { - "customerid": "${ .purchase.customerid }" - } - } - } - ] - } - ], - "compensatedBy": "CancelPurchase", - "transition": "SomeNextWorkflowState" - }, - { - "name": "CancelPurchase", - "type": "operation", - "usedForCompensation": true, - "actions": [ - { - "functionRef": { - "refName": "CreditCustomerFunction", - "arguments": { - "customerid": "${ .purchase.customerid }", - "amount": "${ .purchase.amount }" - } - } - }, - { - "functionRef": { - "refName": "SendPurchaseCancellationEmailFunction", - "arguments": { - "customerid": "${ .purchase.customerid }" - } - } - } - ] - } - ], - "events": [ - { - "name": "NewItemPurchase", - "source": "purchasesource", - "type": "org.purchases" - } - ], - "functions": [ - { - "name": "DebitCustomerFunction", - "operation": "http://myapis.org/application.json#debit" - }, - { - "name": "SendPurchaseConfirmationEmailFunction", - "operation": "http://myapis.org/application.json#confirmationemail" - }, - { - "name": "SendPurchaseCancellationEmailFunction", - "operation": "http://myapis.org/application.json#cancellationemail" - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/features/compensationworkflow.yml b/api/src/test/resources/features/compensationworkflow.yml deleted file mode 100644 index 13bca0d5..00000000 --- a/api/src/test/resources/features/compensationworkflow.yml +++ /dev/null @@ -1,45 +0,0 @@ -id: CompensationWorkflow -name: Compensation Workflow -version: '1.0' -states: - - name: NewItemPurchase - type: event - onEvents: - - eventRefs: - - NewPurchase - actions: - - functionRef: - refName: DebitCustomerFunction - arguments: - customerid: "${ .purchase.customerid }" - amount: "${ .purchase.amount }" - - functionRef: - refName: SendPurchaseConfirmationEmailFunction - arguments: - customerid: "${ .purchase.customerid }" - compensatedBy: CancelPurchase - transition: SomeNextWorkflowState - - name: CancelPurchase - type: operation - usedForCompensation: true - actions: - - functionRef: - refName: CreditCustomerFunction - arguments: - customerid: "${ .purchase.customerid }" - amount: "${ .purchase.amount }" - - functionRef: - refName: SendPurchaseCancellationEmailFunction - arguments: - customerid: "${ .purchase.customerid }" -events: - - name: NewItemPurchase - source: purchasesource - type: org.purchases -functions: - - name: DebitCustomerFunction - operation: http://myapis.org/application.json#debit - - name: SendPurchaseConfirmationEmailFunction - operation: http://myapis.org/application.json#confirmationemail - - name: SendPurchaseCancellationEmailFunction - operation: http://myapis.org/application.json#cancellationemail diff --git a/api/src/test/resources/features/composite.yaml b/api/src/test/resources/features/composite.yaml new file mode 100644 index 00000000..515cda25 --- /dev/null +++ b/api/src/test/resources/features/composite.yaml @@ -0,0 +1,17 @@ +document: + dsl: 1.0.0-alpha1 + namespace: default + name: do + version: 1.0.0 +do: + - compositeExample: + do: + - setRed: + set: + colors: ${ .colors + ["red"] } + - setGreen: + set: + colors: ${ .colors + ["green"] } + - setBlue: + set: + colors: ${ .colors + ["blue"] } \ No newline at end of file diff --git a/api/src/test/resources/features/data-flow.yaml b/api/src/test/resources/features/data-flow.yaml new file mode 100644 index 00000000..bebb2123 --- /dev/null +++ b/api/src/test/resources/features/data-flow.yaml @@ -0,0 +1,14 @@ +document: + dsl: 1.0.0-alpha1 + namespace: default + name: output-filtering + version: 1.0.0 +do: + - getPet: + call: http + with: + method: get + endpoint: + uri: https://petstore.swagger.io/v2/pet/{petId} #simple interpolation, only possible with top level variables + output: + as: .id #filters the output of the http call, using only the id of the returned object diff --git a/api/src/test/resources/features/datainputschemaobj.json b/api/src/test/resources/features/datainputschemaobj.json deleted file mode 100644 index dd9298b2..00000000 --- a/api/src/test/resources/features/datainputschemaobj.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "id": "datainputschemaobj", - "version": "1.0", - "name": "Data Input Schema test", - "dataInputSchema": { - "schema": "somejsonschema.json", - "failOnValidationErrors": false - }, - "start": "TestFunctionRefs", - "states": [ - { - "name": "TestFunctionRefs", - "type": "operation", - "actionMode": "sequential", - "actions": [ - { - "functionRef": "creditCheckFunction" - }, - { - "functionRef": { - "refName": "sendRejectionEmailFunction", - "arguments": { - "applicant": "${ .customer }" - } - } - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/features/datainputschemaobj.yml b/api/src/test/resources/features/datainputschemaobj.yml deleted file mode 100644 index 547eb1c3..00000000 --- a/api/src/test/resources/features/datainputschemaobj.yml +++ /dev/null @@ -1,18 +0,0 @@ -id: datainputschemaobj -version: '1.0' -name: Data Input Schema test -dataInputSchema: - schema: somejsonschema.json - failOnValidationErrors: false -start: TestFunctionRefs -states: - - name: TestFunctionRefs - type: operation - actionMode: sequential - actions: - - functionRef: creditCheckFunction - - functionRef: - refName: sendRejectionEmailFunction - arguments: - applicant: "${ .customer }" - end: true diff --git a/api/src/test/resources/features/datainputschemastring.json b/api/src/test/resources/features/datainputschemastring.json deleted file mode 100644 index 159e715d..00000000 --- a/api/src/test/resources/features/datainputschemastring.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "id": "datainputschemaobj", - "version": "1.0", - "name": "Data Input Schema test", - "dataInputSchema": "somejsonschema.json", - "start": "TestFunctionRefs", - "states": [ - { - "name": "TestFunctionRefs", - "type": "operation", - "actionMode": "sequential", - "actions": [ - { - "functionRef": "creditCheckFunction" - }, - { - "functionRef": { - "refName": "sendRejectionEmailFunction", - "arguments": { - "applicant": "${ .customer }" - } - } - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/features/datainputschemastring.yml b/api/src/test/resources/features/datainputschemastring.yml deleted file mode 100644 index ac9f3556..00000000 --- a/api/src/test/resources/features/datainputschemastring.yml +++ /dev/null @@ -1,16 +0,0 @@ -id: datainputschemaobj -version: '1.0' -name: Data Input Schema test -dataInputSchema: somejsonschema.json -start: TestFunctionRefs -states: - - name: TestFunctionRefs - type: operation - actionMode: sequential - actions: - - functionRef: creditCheckFunction - - functionRef: - refName: sendRejectionEmailFunction - arguments: - applicant: "${ .customer }" - end: true diff --git a/api/src/test/resources/features/emit.yaml b/api/src/test/resources/features/emit.yaml new file mode 100644 index 00000000..983407d9 --- /dev/null +++ b/api/src/test/resources/features/emit.yaml @@ -0,0 +1,14 @@ +document: + dsl: 1.0.0-alpha1 + namespace: default + name: emit + version: 1.0.0 +do: + - emitEvent: + emit: + event: + with: + source: https://fake-source.com + type: com.fake-source.user.greeted.v1 + data: + greetings: ${ "Hello \(.user.firstName) \(.user.lastName)!" } \ No newline at end of file diff --git a/api/src/test/resources/features/expressionlang.json b/api/src/test/resources/features/expressionlang.json deleted file mode 100644 index 682f74e1..00000000 --- a/api/src/test/resources/features/expressionlang.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "id": "expressionlang", - "version": "1.0", - "name": "Custom expression lang", - "expressionLang": "abc", - "start": "TestFunctionRefs", - "states": [ - { - "name": "TestFunctionRefs", - "type": "operation", - "actionMode": "sequential", - "actions": [ - { - "functionRef": "creditCheckFunction" - }, - { - "functionRef": { - "refName": "sendRejectionEmailFunction", - "arguments": { - "applicant": "${ .customer }" - } - } - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/features/expressionlang.yml b/api/src/test/resources/features/expressionlang.yml deleted file mode 100644 index a6c84803..00000000 --- a/api/src/test/resources/features/expressionlang.yml +++ /dev/null @@ -1,16 +0,0 @@ -id: expressionlang -version: '1.0' -name: Custom expression lang -expressionLang: abc -start: TestFunctionRefs -states: - - name: TestFunctionRefs - type: operation - actionMode: sequential - actions: - - functionRef: creditCheckFunction - - functionRef: - refName: sendRejectionEmailFunction - arguments: - applicant: "${ .customer }" - end: true \ No newline at end of file diff --git a/api/src/test/resources/features/flow.yaml b/api/src/test/resources/features/flow.yaml new file mode 100644 index 00000000..6bd8a6e7 --- /dev/null +++ b/api/src/test/resources/features/flow.yaml @@ -0,0 +1,15 @@ +document: + dsl: 1.0.0-alpha1 + namespace: default + name: implicit-sequence + version: 1.0.0 +do: + - setRed: + set: + colors: '${ .colors + [ "red" ] }' + - setGreen: + set: + colors: '${ .colors + [ "green" ] }' + - setBlue: + set: + colors: '${ .colors + [ "blue" ] }' diff --git a/api/src/test/resources/features/for.yaml b/api/src/test/resources/features/for.yaml new file mode 100644 index 00000000..662dff91 --- /dev/null +++ b/api/src/test/resources/features/for.yaml @@ -0,0 +1,14 @@ +document: + dsl: 1.0.0-alpha1 + namespace: default + name: for + version: 1.0.0 +do: + - loopColors: + for: + each: color + in: '.colors' + do: + - markProcessed: + set: + processed: '${ { colors: (.processed.colors + [ $color ]), indexes: (.processed.indexes + [ $index ])} }' \ No newline at end of file diff --git a/api/src/test/resources/features/functionrefjsonparams.json b/api/src/test/resources/features/functionrefjsonparams.json deleted file mode 100644 index b872ab81..00000000 --- a/api/src/test/resources/features/functionrefjsonparams.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "id": "functionrefparams", - "version": "1.0", - "name": "Function Ref Params Test", - "start": "AddPluto", - "states": [ - { - "name": "AddPluto", - "type": "operation", - "actions": [ - { - "functionRef": { - "refName": "addPet", - "arguments": { - "body": { - "name": "Pluto", - "tag": "${ .pet.tagnumber }" - }, - "id": 123, - "address": "My Address, 123 MyCity, MyCountry", - "owner": "${ .owner.name }" - } - } - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/features/functionrefjsonparams.yml b/api/src/test/resources/features/functionrefjsonparams.yml deleted file mode 100644 index 9df672e1..00000000 --- a/api/src/test/resources/features/functionrefjsonparams.yml +++ /dev/null @@ -1,18 +0,0 @@ -id: functionrefparams -version: '1.0' -name: Function Ref Params Test -start: AddPluto -states: - - name: AddPluto - type: operation - actions: - - functionRef: - refName: addPet - arguments: - body: - name: Pluto - tag: "${ .pet.tagnumber }" - id: 123 - address: My Address, 123 MyCity, MyCountry - owner: "${ .owner.name }" - end: true diff --git a/api/src/test/resources/features/functionrefnoparams.json b/api/src/test/resources/features/functionrefnoparams.json deleted file mode 100644 index de4ddf88..00000000 --- a/api/src/test/resources/features/functionrefnoparams.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "id": "functionrefparams", - "version": "1.0", - "name": "Function Ref Params Test", - "start": "AddPluto", - "states": [ - { - "name": "AddPluto", - "type": "operation", - "actions": [ - { - "functionRef": { - "refName": "addPet" - } - }, - { - "functionRef": "addPet" - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/features/functionrefnoparams.yml b/api/src/test/resources/features/functionrefnoparams.yml deleted file mode 100644 index 1e88798b..00000000 --- a/api/src/test/resources/features/functionrefnoparams.yml +++ /dev/null @@ -1,12 +0,0 @@ -id: functionrefparams -version: '1.0' -name: Function Ref Params Test -start: AddPluto -states: - - name: AddPluto - type: operation - actions: - - functionRef: - refName: addPet - - functionRef: addPet - end: true \ No newline at end of file diff --git a/api/src/test/resources/features/functionrefs.json b/api/src/test/resources/features/functionrefs.json deleted file mode 100644 index 9125bc78..00000000 --- a/api/src/test/resources/features/functionrefs.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "id": "functionrefs", - "version": "1.0", - "name": "Customer Credit Check Workflow", - "description": "Perform Customer Credit Check", - "start": "TestFunctionRef", - "functions": [ - { - "name": "creditCheckFunction", - "operation": "http://myapis.org/creditcheckapi.json#doCreditCheck" - }, - { - "name": "sendRejectionEmailFunction", - "operation": "http://myapis.org/creditcheckapi.json#rejectionEmail" - } - ], - "states": [ - { - "name": "TestFunctionRefs", - "type": "operation", - "actionMode": "sequential", - "actions": [ - { - "functionRef": "creditCheckFunction" - }, - { - "functionRef": { - "refName": "sendRejectionEmailFunction", - "arguments": { - "applicant": "${ .customer }" - } - } - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/features/functionrefs.yml b/api/src/test/resources/features/functionrefs.yml deleted file mode 100644 index 2f435ccd..00000000 --- a/api/src/test/resources/features/functionrefs.yml +++ /dev/null @@ -1,21 +0,0 @@ -id: functionrefs -version: '1.0' -name: Customer Credit Check Workflow -description: Perform Customer Credit Check -start: TestFunctionRefs -functions: - - name: creditCheckFunction - operation: http://myapis.org/creditcheckapi.json#doCreditCheck - - name: sendRejectionEmailFunction - operation: http://myapis.org/creditcheckapi.json#rejectionEmail -states: - - name: TestFunctionRefs - type: operation - actionMode: sequential - actions: - - functionRef: creditCheckFunction - - functionRef: - refName: sendRejectionEmailFunction - arguments: - applicant: "${ .customer }" - end: true \ No newline at end of file diff --git a/api/src/test/resources/features/functiontypes.json b/api/src/test/resources/features/functiontypes.json deleted file mode 100644 index 712b94f1..00000000 --- a/api/src/test/resources/features/functiontypes.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "id": "functiontypes", - "version": "1.0", - "name": "Function Types Workflow", - "description": "Determine if applicant request is valid", - "start": "CheckFunctions", - "functions": [ - { - "name": "restFunction", - "operation": "http://myapis.org/applicationapi.json#emailRejection" - }, - { - "name": "expressionFunction", - "operation": ".my.data", - "type" : "expression" - } - ], - "states":[ - { - "name":"CheckFunctions", - "type":"operation", - "actions":[ - { - "functionRef": { - "refName": "restFunction", - "arguments": { - "data": "${ fn(expressionFunction) }" - } - } - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/features/functiontypes.yml b/api/src/test/resources/features/functiontypes.yml deleted file mode 100644 index b771c226..00000000 --- a/api/src/test/resources/features/functiontypes.yml +++ /dev/null @@ -1,20 +0,0 @@ -id: functiontypes -version: '1.0' -name: Function Types Workflow -description: Determine if applicant request is valid -start: CheckFunctions -functions: - - name: restFunction - operation: http://myapis.org/applicationapi.json#emailRejection - - name: expressionFunction - operation: ".my.data" - type: expression -states: - - name: CheckFunctions - type: operation - actions: - - functionRef: - refName: restFunction - arguments: - data: "${ fn(expressionFunction) }" - end: true \ No newline at end of file diff --git a/api/src/test/resources/features/keepactiveexectimeout.json b/api/src/test/resources/features/keepactiveexectimeout.json deleted file mode 100644 index 0864a0b1..00000000 --- a/api/src/test/resources/features/keepactiveexectimeout.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "id": "keepactiveexectimeout", - "name": "Keep Active and Exec Timeout Test Workflow", - "version": "1.0", - "execTimeout": { - "duration": "PT1H", - "runBefore": "GenerateReport" - }, - "keepActive": true, - "states": [ - ] -} \ No newline at end of file diff --git a/api/src/test/resources/features/keepactiveexectimeout.yml b/api/src/test/resources/features/keepactiveexectimeout.yml deleted file mode 100644 index ca55979a..00000000 --- a/api/src/test/resources/features/keepactiveexectimeout.yml +++ /dev/null @@ -1,8 +0,0 @@ -id: keepactiveexectimeout -name: Keep Active and Exec Timeout Test Workflow -version: '1.0' -execTimeout: - duration: PT1H - runBefore: GenerateReport -keepActive: true -states: [] \ No newline at end of file diff --git a/api/src/test/resources/features/listen-to-any.yaml b/api/src/test/resources/features/listen-to-any.yaml new file mode 100644 index 00000000..fa8794d3 --- /dev/null +++ b/api/src/test/resources/features/listen-to-any.yaml @@ -0,0 +1,16 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: listen-to-any + version: '0.1.0' +do: + - callDoctor: + listen: + to: + any: + - with: + type: com.fake-hospital.vitals.measurements.temperature + data: ${ .temperature > 38 } + - with: + type: com.fake-hospital.vitals.measurements.bpm + data: ${ .bpm < 60 or .bpm > 100 } \ No newline at end of file diff --git a/api/src/test/resources/features/longstart.json b/api/src/test/resources/features/longstart.json deleted file mode 100644 index 8347cbe1..00000000 --- a/api/src/test/resources/features/longstart.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "id": "longstart", - "version": "1.0", - "name": "Long start", - "start": { - "stateName": "TestFunctionRefs", - "schedule": { - "cron": "0 0/15 * * * ?" - } - }, - "states": [ - { - "name": "TestFunctionRefs", - "type": "operation", - "actionMode": "sequential", - "actions": [ - { - "functionRef": "creditCheckFunction" - }, - { - "functionRef": { - "refName": "sendRejectionEmailFunction", - "arguments": { - "applicant": "${ .customer }" - } - } - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/features/longstart.yml b/api/src/test/resources/features/longstart.yml deleted file mode 100644 index 4c5046d8..00000000 --- a/api/src/test/resources/features/longstart.yml +++ /dev/null @@ -1,18 +0,0 @@ -id: longstart -version: '1.0' -name: Long start -start: - stateName: TestFunctionRefs - schedule: - cron: 0 0/15 * * * ? -states: - - name: TestFunctionRefs - type: operation - actionMode: sequential - actions: - - functionRef: creditCheckFunction - - functionRef: - refName: sendRejectionEmailFunction - arguments: - applicant: "${ .customer }" - end: true \ No newline at end of file diff --git a/api/src/test/resources/features/raise.yaml b/api/src/test/resources/features/raise.yaml new file mode 100644 index 00000000..7745162c --- /dev/null +++ b/api/src/test/resources/features/raise.yaml @@ -0,0 +1,13 @@ +document: + dsl: 1.0.0-alpha1 + namespace: default + name: raise-custom-error + version: 1.0.0 +do: + - raiseError: + raise: + error: + status: 400 + type: https://serverlessworkflow.io/errors/types/compliance + title: Compliance Error + instance: raiseError \ No newline at end of file diff --git a/api/src/test/resources/features/retriesprops.json b/api/src/test/resources/features/retriesprops.json deleted file mode 100644 index ade59971..00000000 --- a/api/src/test/resources/features/retriesprops.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "id": "TestRetriesProps", - "name": "Retries props test", - "version": "1.0", - "start": "Test State", - "retries": [ - { - "name": "Test Retries", - "delay": "PT1M", - "maxDelay": "PT2M", - "increment": "PT2S", - "multiplier": "1.2", - "maxAttempts": "20", - "jitter": "0.4" - } - ], - "states": [ - { - "name": "Test States", - "type": "operation", - "actions": [ - ], - "onErrors": [ - { - "error": "TimeoutError", - "code": "500", - "retryRef": "Test Retries", - "end": true - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/features/retriesprops.yml b/api/src/test/resources/features/retriesprops.yml deleted file mode 100644 index 8f6c801e..00000000 --- a/api/src/test/resources/features/retriesprops.yml +++ /dev/null @@ -1,22 +0,0 @@ -id: TestRetriesProps -name: Retries props test -version: '1.0' -start: Test State -retries: - - name: Test Retries - delay: PT1M - maxDelay: PT2M - increment: PT2S - multiplier: '1.2' - maxAttempts: '20' - jitter: '0.4' -states: - - name: Test States - type: operation - actions: [] - onErrors: - - error: TimeoutError - code: '500' - retryRef: Test Retries - end: true - end: true diff --git a/api/src/test/resources/features/set.yaml b/api/src/test/resources/features/set.yaml new file mode 100644 index 00000000..dfebbf2d --- /dev/null +++ b/api/src/test/resources/features/set.yaml @@ -0,0 +1,11 @@ +document: + dsl: 1.0.0-alpha1 + namespace: default + name: set + version: 1.0.0 +do: + - setShape: + set: + shape: circle + size: ${ .configuration.size } + fill: ${ .configuration.fill } diff --git a/api/src/test/resources/features/shortstart.json b/api/src/test/resources/features/shortstart.json deleted file mode 100644 index 7f3adb75..00000000 --- a/api/src/test/resources/features/shortstart.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "id": "shortstart", - "version": "1.0", - "name": "Short start", - "start": "TestFunctionRefs", - "states": [ - { - "name": "TestFunctionRefs", - "type": "operation", - "actionMode": "sequential", - "actions": [ - { - "functionRef": "creditCheckFunction" - }, - { - "functionRef": { - "refName": "sendRejectionEmailFunction", - "arguments": { - "applicant": "${ .customer }" - } - } - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/features/shortstart.yml b/api/src/test/resources/features/shortstart.yml deleted file mode 100644 index 4d3e3076..00000000 --- a/api/src/test/resources/features/shortstart.yml +++ /dev/null @@ -1,15 +0,0 @@ -id: shortstart -version: '1.0' -name: Short start -start: TestFunctionRefs -states: - - name: TestFunctionRefs - type: operation - actionMode: sequential - actions: - - functionRef: creditCheckFunction - - functionRef: - refName: sendRejectionEmailFunction - arguments: - applicant: "${ .customer }" - end: true \ No newline at end of file diff --git a/api/src/test/resources/features/simplecron.json b/api/src/test/resources/features/simplecron.json deleted file mode 100644 index c2596dc6..00000000 --- a/api/src/test/resources/features/simplecron.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "id": "checkInbox", - "name": "Check Inbox Workflow", - "description": "Periodically Check Inbox", - "version": "1.0", - "start": { - "stateName": "CheckInbox", - "schedule": { - "cron": "0 0/15 * * * ?" - } - }, - "functions": [ - { - "name": "checkInboxFunction", - "operation": "http://myapis.org/inboxapi.json#checkNewMessages" - }, - { - "name": "sendTextFunction", - "operation": "http://myapis.org/inboxapi.json#sendText" - } - ], - "states": [ - { - "name": "CheckInbox", - "type": "operation", - "actionMode": "sequential", - "actions": [ - { - "functionRef": "checkInboxFunction" - } - ], - "transition": "SendTextForHighPrioriry" - }, - { - "name": "SendTextForHighPrioriry", - "type": "foreach", - "inputCollection": "${ .messages }", - "iterationParam": "singlemessage", - "actions": [ - { - "functionRef": { - "refName": "sendTextFunction", - "arguments": { - "message": "${ .singlemessage }" - } - } - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/features/simplecron.yml b/api/src/test/resources/features/simplecron.yml deleted file mode 100644 index 6221c5ce..00000000 --- a/api/src/test/resources/features/simplecron.yml +++ /dev/null @@ -1,30 +0,0 @@ -id: checkInbox -name: Check Inbox Workflow -description: Periodically Check Inbox -version: '1.0' -start: - stateName: CheckInbox - schedule: - cron: 0 0/15 * * * ? -functions: - - name: checkInboxFunction - operation: http://myapis.org/inboxapi.json#checkNewMessages - - name: sendTextFunction - operation: http://myapis.org/inboxapi.json#sendText -states: - - name: CheckInbox - type: operation - actionMode: sequential - actions: - - functionRef: checkInboxFunction - transition: SendTextForHighPrioriry - - name: SendTextForHighPrioriry - type: foreach - inputCollection: "${ .messages }" - iterationParam: singlemessage - actions: - - functionRef: - refName: sendTextFunction - arguments: - message: "${ .singlemessage }" - end: true \ No newline at end of file diff --git a/api/src/test/resources/features/simpleschedule.json b/api/src/test/resources/features/simpleschedule.json deleted file mode 100644 index 00ba0f69..00000000 --- a/api/src/test/resources/features/simpleschedule.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "id": "handleCarAuctionBid", - "version": "1.0", - "name": "Car Auction Bidding Workflow", - "description": "Store a single bid whole the car auction is active", - "start": { - "stateName": "StoreCarAuctionBid", - "schedule": "2020-03-20T09:00:00Z/2020-03-20T15:00:00Z" - }, - "functions": [ - { - "name": "StoreBidFunction", - "operation": "http://myapis.org/carauctionapi.json#storeBid" - } - ], - "events": [ - { - "name": "CarBidEvent", - "type": "carBidMadeType", - "source": "carBidEventSource" - } - ], - "states": [ - { - "name": "StoreCarAuctionBid", - "type": "event", - "exclusive": true, - "onEvents": [ - { - "eventRefs": [ - "CarBidEvent" - ], - "actions": [ - { - "functionRef": { - "refName": "StoreBidFunction", - "arguments": { - "bid": "${ .bid }" - } - } - } - ] - } - ], - "end": { - "terminate": true - } - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/features/simpleschedule.yml b/api/src/test/resources/features/simpleschedule.yml deleted file mode 100644 index 1eb8a495..00000000 --- a/api/src/test/resources/features/simpleschedule.yml +++ /dev/null @@ -1,28 +0,0 @@ -id: handleCarAuctionBid -version: '1.0' -name: Car Auction Bidding Workflow -description: Store a single bid whole the car auction is active -start: - stateName: StoreCarAuctionBid - schedule: 2020-03-20T09:00:00Z/2020-03-20T15:00:00Z -functions: - - name: StoreBidFunction - operation: http://myapis.org/carauctionapi.json#storeBid -events: - - name: CarBidEvent - type: carBidMadeType - source: carBidEventSource -states: - - name: StoreCarAuctionBid - type: event - exclusive: true - onEvents: - - eventRefs: - - CarBidEvent - actions: - - functionRef: - refName: StoreBidFunction - arguments: - bid: "${ .bid }" - end: - terminate: true \ No newline at end of file diff --git a/api/src/test/resources/features/switch.yaml b/api/src/test/resources/features/switch.yaml new file mode 100644 index 00000000..aa073fed --- /dev/null +++ b/api/src/test/resources/features/switch.yaml @@ -0,0 +1,29 @@ +document: + dsl: 1.0.0-alpha1 + namespace: default + name: switch-match + version: 1.0.0 +do: + - switchColor: + switch: + - red: + when: '.color == "red"' + then: setRed + - green: + when: '.color == "green"' + then: setGreen + - blue: + when: '.color == "blue"' + then: setBlue + - setRed: + set: + colors: '${ .colors + [ "red" ] }' + then: end + - setGreen: + set: + colors: '${ .colors + [ "green" ] }' + then: end + - setBlue: + set: + colors: '${ .colors + [ "blue" ] }' + then: end diff --git a/api/src/test/resources/features/transitions.json b/api/src/test/resources/features/transitions.json deleted file mode 100644 index fde5ee72..00000000 --- a/api/src/test/resources/features/transitions.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "id": "transitions", - "version": "1.0", - "name": "Transitions Workflow", - "start": "DifferentTransitionsTestState", - "description": "Transitions Workflow", - "functions": "features/applicantrequestfunctions.json", - "retries": "features/applicantrequestretries.json", - "states":[ - { - "name":"DifferentTransitionsTestState", - "type":"switch", - "dataConditions": [ - { - "condition": "${ .applicants[?(@.age >= 18)] }", - "transition": "StartApplication" - }, - { - "condition": "${ .applicants[?(@.age < 18)] }", - "transition": { - "nextState": "RejectApplication", - "produceEvents": [ - { - "eventRef": "provisioningCompleteEvent", - "data": "${ .provisionedOrders }" - } - ] - } - } - ], - "default": { - "transition": { - "nextState": "RejectApplication", - "compensate": true - } - } - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/features/transitions.yml b/api/src/test/resources/features/transitions.yml deleted file mode 100644 index b3528b1a..00000000 --- a/api/src/test/resources/features/transitions.yml +++ /dev/null @@ -1,23 +0,0 @@ -id: transitions -version: '1.0' -name: Transitions Workflow -description: Transitions Workflow -start: DifferentTransitionsTestState -functions: features/applicantrequestfunctions.json -retries: features/applicantrequestretries.json -states: - - name: DifferentTransitionsTestState - type: switch - dataConditions: - - condition: "${ .applicants[?(@.age >= 18)] }" - transition: StartApplication - - condition: "${ .applicants[?(@.age < 18)] }" - transition: - nextState: RejectApplication - produceEvents: - - eventRef: provisioningCompleteEvent - data: "${ .provisionedOrders }" - default: - transition: - nextState: RejectApplication - compensate: true \ No newline at end of file diff --git a/api/src/test/resources/features/try.yaml b/api/src/test/resources/features/try.yaml new file mode 100644 index 00000000..ec19194d --- /dev/null +++ b/api/src/test/resources/features/try.yaml @@ -0,0 +1,24 @@ +document: + dsl: 1.0.0-alpha1 + namespace: default + name: try-catch-404 + version: 1.0.0 +do: + - tryGetPet: + try: + - getPet: + call: http + with: + method: get + endpoint: + uri: https://petstore.swagger.io/v2/pet/getPetByName/{petName} + catch: + errors: + with: + type: https://serverlessworkflow.io/dsl/errors/types/communication + status: 404 + as: err + do: + - setError: + set: + error: ${ $err } diff --git a/api/src/test/resources/features/vetappointment.json b/api/src/test/resources/features/vetappointment.json deleted file mode 100644 index bbcccaea..00000000 --- a/api/src/test/resources/features/vetappointment.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "id": "VetAppointmentWorkflow", - "name": "Vet Appointment Workflow", - "description": "Vet service call via events", - "version": "1.0", - "start": "MakeVetAppointmentState", - "events": "features/vetappointmenteventrefs.json", - "retries": "features/vetappointmentretries.json", - "states": [ - { - "name": "MakeVetAppointmentState", - "type": "operation", - "actions": [ - { - "name": "MakeAppointmentAction", - "eventRef": { - "triggerEventRef": "MakeVetAppointment", - "data": "${ .patientInfo }", - "resultEventRef": "VetAppointmentInfo" - }, - "actionDataFilter": { - "results": "${ .appointmentInfo }" - }, - "timeout": "PT15M" - } - ], - "onErrors": [ - { - "error": "TimeoutError", - "code": "500", - "retryRef": "TimeoutRetryStrategy", - "end": true - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/features/vetappointment.yml b/api/src/test/resources/features/vetappointment.yml deleted file mode 100644 index b137a01d..00000000 --- a/api/src/test/resources/features/vetappointment.yml +++ /dev/null @@ -1,25 +0,0 @@ -id: VetAppointmentWorkflow -name: Vet Appointment Workflow -description: Vet service call via events -version: '1.0' -start: MakeVetAppointmentState -events: features/vetappointmenteventrefs.yml -retries: features/vetappointmentretries.yml -states: - - name: MakeVetAppointmentState - type: operation - actions: - - name: MakeAppointmentAction - eventRef: - triggerEventRef: MakeVetAppointment - data: "${ .patientInfo }" - resultEventRef: VetAppointmentInfo - actionDataFilter: - results: "${ .appointmentInfo }" - timeout: PT15M - onErrors: - - error: TimeoutError - code: '500' - retryRef: TimeoutRetryStrategy - end: true - end: true diff --git a/api/src/test/resources/features/vetappointmenteventrefs.json b/api/src/test/resources/features/vetappointmenteventrefs.json deleted file mode 100644 index 6d021888..00000000 --- a/api/src/test/resources/features/vetappointmenteventrefs.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "events": [ - { - "name": "MakeVetAppointment", - "source": "VetServiceSoure", - "kind": "produced" - }, - { - "name": "VetAppointmentInfo", - "source": "VetServiceSource", - "kind": "consumed" - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/features/vetappointmenteventrefs.yml b/api/src/test/resources/features/vetappointmenteventrefs.yml deleted file mode 100644 index 922e6bd7..00000000 --- a/api/src/test/resources/features/vetappointmenteventrefs.yml +++ /dev/null @@ -1,7 +0,0 @@ -events: - - name: MakeVetAppointment - source: VetServiceSoure - kind: produced - - name: VetAppointmentInfo - source: VetServiceSource - kind: consumed diff --git a/api/src/test/resources/features/vetappointmentretries.json b/api/src/test/resources/features/vetappointmentretries.json deleted file mode 100644 index 40f83b55..00000000 --- a/api/src/test/resources/features/vetappointmentretries.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "retries": [ - { - "name": "TimeoutRetryStrategy", - "delay": "PT1M", - "maxAttempts": "5" - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/features/vetappointmentretries.yml b/api/src/test/resources/features/vetappointmentretries.yml deleted file mode 100644 index fa4c810d..00000000 --- a/api/src/test/resources/features/vetappointmentretries.yml +++ /dev/null @@ -1,4 +0,0 @@ -retries: - - name: TimeoutRetryStrategy - delay: PT1M - maxAttempts: '5' diff --git a/code-of-conduct.md b/code-of-conduct.md index ebdded50..97a8526a 100644 --- a/code-of-conduct.md +++ b/code-of-conduct.md @@ -1,59 +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 -(http://contributor-covenant.org), version 1.2.0, available at -http://contributor-covenant.org/version/1/2/0/ - - -### 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/.gitignore b/diagram/.gitignore deleted file mode 100644 index d4dfde66..00000000 --- a/diagram/.gitignore +++ /dev/null @@ -1,31 +0,0 @@ -HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/** -!**/src/test/** - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -build/ - -### VS Code ### -.vscode/ \ No newline at end of file diff --git a/diagram/pom.xml b/diagram/pom.xml deleted file mode 100644 index 64f32881..00000000 --- a/diagram/pom.xml +++ /dev/null @@ -1,90 +0,0 @@ - - 4.0.0 - - - io.serverlessworkflow - serverlessworkflow-parent - 3.0.0-SNAPSHOT - - - serverlessworkflow-diagram - Serverless Workflow :: Diagram - ${project.parent.version} - jar - Java SDK for Serverless Workflow Specification - - - - org.slf4j - slf4j-api - - - - io.serverlessworkflow - serverlessworkflow-api - ${project.version} - - - - org.apache.commons - commons-lang3 - - - - org.thymeleaf - thymeleaf - - - net.sourceforge.plantuml - plantuml - - - guru.nidi - graphviz-java - - - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - org.junit.jupiter - junit-jupiter-params - test - - - org.mockito - mockito-core - test - - - ch.qos.logback - logback-classic - test - - - org.assertj - assertj-core - test - - - org.hamcrest - hamcrest-library - test - - - org.skyscreamer - jsonassert - test - - - \ 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 deleted file mode 100644 index 5848da23..00000000 --- a/diagram/src/main/java/io/serverlessworkflow/diagram/WorkflowDiagramImpl.java +++ /dev/null @@ -1,69 +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.diagram; - -import io.serverlessworkflow.api.Workflow; -import io.serverlessworkflow.api.interfaces.WorkflowDiagram; -import io.serverlessworkflow.diagram.utils.WorkflowToPlantuml; -import net.sourceforge.plantuml.FileFormat; -import net.sourceforge.plantuml.FileFormatOption; -import net.sourceforge.plantuml.SourceStringReader; - -import java.io.ByteArrayOutputStream; -import java.nio.charset.Charset; - -public class WorkflowDiagramImpl implements WorkflowDiagram { - - - @SuppressWarnings("unused") - private String source; - private Workflow workflow; - private boolean showLegend = false; - - @Override - public WorkflowDiagram setWorkflow(Workflow workflow) { - this.workflow = workflow; - this.source = Workflow.toJson(workflow); - return this; - } - - @Override - public WorkflowDiagram setSource(String source) { - this.source = source; - this.workflow = Workflow.fromSource(source); - 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); - SourceStringReader reader = new SourceStringReader(diagramSource); - final ByteArrayOutputStream os = new ByteArrayOutputStream(); - reader.generateImage(os, new FileFormatOption(FileFormat.SVG)); - os.close(); - return new String(os.toByteArray(), Charset.forName("UTF-8")); - } - - @Override - public WorkflowDiagram showLegend(boolean showLegend) { - this.showLegend = showLegend; - return this; - } -} diff --git a/diagram/src/main/java/io/serverlessworkflow/diagram/config/ThymeleafConfig.java b/diagram/src/main/java/io/serverlessworkflow/diagram/config/ThymeleafConfig.java deleted file mode 100644 index d490746e..00000000 --- a/diagram/src/main/java/io/serverlessworkflow/diagram/config/ThymeleafConfig.java +++ /dev/null @@ -1,42 +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.diagram.config; - -import org.thymeleaf.TemplateEngine; -import org.thymeleaf.templatemode.TemplateMode; -import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver; -import org.thymeleaf.templateresolver.ITemplateResolver; - -public class ThymeleafConfig { - public static TemplateEngine templateEngine; - - static { - templateEngine = new TemplateEngine(); - templateEngine.addTemplateResolver(plantUmlTemplateResolver()); - } - - private static ITemplateResolver plantUmlTemplateResolver() { - ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver(); - templateResolver.setPrefix("/templates/plantuml/"); - templateResolver.setSuffix(".txt"); - templateResolver.setTemplateMode(TemplateMode.TEXT); - templateResolver.setCharacterEncoding("UTF8"); - templateResolver.setCheckExistence(true); - templateResolver.setCacheable(false); - return templateResolver; - } -} diff --git a/diagram/src/main/java/io/serverlessworkflow/diagram/model/ModelConnection.java b/diagram/src/main/java/io/serverlessworkflow/diagram/model/ModelConnection.java deleted file mode 100644 index 70c5e934..00000000 --- a/diagram/src/main/java/io/serverlessworkflow/diagram/model/ModelConnection.java +++ /dev/null @@ -1,70 +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.diagram.model; - -import io.serverlessworkflow.diagram.utils.WorkflowDiagramUtils; - -public class ModelConnection { - private String left; - private String right; - private String desc; - - public ModelConnection(String left, String right, String desc) { - this.left = left.replaceAll("\\s", ""); - this.right = right.replaceAll("\\s", ""); - this.desc = desc; - } - - @Override - public String toString() { - StringBuilder retBuff = new StringBuilder(); - retBuff.append(System.lineSeparator()); - retBuff.append(left.equals(WorkflowDiagramUtils.wfStart) ? WorkflowDiagramUtils.startEnd : left); - retBuff.append(WorkflowDiagramUtils.connection); - retBuff.append(right.equals(WorkflowDiagramUtils.wfEnd) ? WorkflowDiagramUtils.startEnd : right); - if (desc != null && desc.trim().length() > 0) { - retBuff.append(WorkflowDiagramUtils.description).append(desc); - } - retBuff.append(System.lineSeparator()); - - return retBuff.toString(); - } - - public String getLeft() { - return left; - } - - public void setLeft(String left) { - this.left = left; - } - - public String getRight() { - return right; - } - - public void setRight(String right) { - this.right = right; - } - - public String getDesc() { - return desc; - } - - public void setDesc(String desc) { - this.desc = desc; - } -} diff --git a/diagram/src/main/java/io/serverlessworkflow/diagram/model/ModelState.java b/diagram/src/main/java/io/serverlessworkflow/diagram/model/ModelState.java deleted file mode 100644 index 0cbea40e..00000000 --- a/diagram/src/main/java/io/serverlessworkflow/diagram/model/ModelState.java +++ /dev/null @@ -1,52 +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.diagram.model; - -import io.serverlessworkflow.diagram.utils.WorkflowDiagramUtils; - -import java.util.ArrayList; -import java.util.List; - -public class ModelState { - - @SuppressWarnings("unused") - private String name; - private String noSpaceName; - private List stateInfo = new ArrayList<>(); - - public ModelState(String name) { - this.name = name; - this.noSpaceName = name.replaceAll("\\s", ""); - } - - public void addInfo(String info) { - stateInfo.add(info); - } - - @Override - public String toString() { - StringBuilder retBuff = new StringBuilder(); - retBuff.append(System.lineSeparator()); - for(String info : stateInfo) { - retBuff.append(noSpaceName).append(WorkflowDiagramUtils.description) - .append(info).append(System.lineSeparator()); - } - retBuff.append(System.lineSeparator()); - - return retBuff.toString(); - } -} diff --git a/diagram/src/main/java/io/serverlessworkflow/diagram/model/ModelStateDef.java b/diagram/src/main/java/io/serverlessworkflow/diagram/model/ModelStateDef.java deleted file mode 100644 index 8eb3dca2..00000000 --- a/diagram/src/main/java/io/serverlessworkflow/diagram/model/ModelStateDef.java +++ /dev/null @@ -1,44 +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.diagram.model; - -import io.serverlessworkflow.diagram.utils.WorkflowDiagramUtils; - -public class ModelStateDef { - private String name; - private String type; - private String noSpaceName; - - public ModelStateDef(String name, String type) { - this.name = name; - this.type = type; - this.noSpaceName = name.replaceAll("\\s", ""); - } - - @Override - public String toString() { - StringBuilder retBuff = new StringBuilder(); - retBuff.append(WorkflowDiagramUtils.stateDef) - .append(noSpaceName) - .append(WorkflowDiagramUtils.stateAsName) - .append("\"" + name + "\"") - .append(WorkflowDiagramUtils.typeDefStart) - .append(type) - .append(WorkflowDiagramUtils.typeDefEnd); - return retBuff.toString(); - } -} diff --git a/diagram/src/main/java/io/serverlessworkflow/diagram/model/WorkflowDiagramModel.java b/diagram/src/main/java/io/serverlessworkflow/diagram/model/WorkflowDiagramModel.java deleted file mode 100644 index 9d561561..00000000 --- a/diagram/src/main/java/io/serverlessworkflow/diagram/model/WorkflowDiagramModel.java +++ /dev/null @@ -1,419 +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.diagram.model; - -import io.serverlessworkflow.api.Workflow; -import io.serverlessworkflow.api.interfaces.State; -import io.serverlessworkflow.api.states.*; -import io.serverlessworkflow.api.switchconditions.DataCondition; -import io.serverlessworkflow.api.switchconditions.EventCondition; -import io.serverlessworkflow.diagram.utils.WorkflowDiagramUtils; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -public class WorkflowDiagramModel { - private Workflow workflow; - - private String title; - private String legend; - private String footer; - private List modelStateDefs = new ArrayList<>(); - private List modelStates = new ArrayList<>(); - private List modelConnections = new ArrayList<>(); - private boolean showLegend; - - public WorkflowDiagramModel(Workflow workflow, boolean showLegend) { - this.workflow = workflow; - this.showLegend = showLegend; - inspect(workflow); - } - - private void inspect(Workflow workflow) { - // title - setTitle(workflow.getName()); - if (workflow.getVersion() != null && workflow.getVersion().trim().length() > 0) { - StringBuilder titleBuf = new StringBuilder() - .append(workflow.getName()) - .append(WorkflowDiagramUtils.versionSeparator) - .append(workflow.getVersion()); - setTitle(titleBuf.toString()); - } - - // legend - if (workflow.getDescription() != null && workflow.getDescription().trim().length() > 0) { - StringBuilder legendBuff = new StringBuilder() - .append(WorkflowDiagramUtils.legendStart) - .append(workflow.getDescription()) - .append(WorkflowDiagramUtils.legendEnd); - setLegend(legendBuff.toString()); - } else { - setLegend(""); - } - - // footer - setFooter(WorkflowDiagramUtils.footer); - - // state definitions - inspectStateDefinitions(workflow); - - // states info - inspectStatesInfo(workflow); - - // states connections - inspectStatesConnections(workflow); - - } - - private void inspectStateDefinitions(Workflow workflow) { - for(State state : workflow.getStates()) { - modelStateDefs.add(new ModelStateDef(state.getName(), state.getType().value())); - } - } - - private void inspectStatesConnections(Workflow workflow) { - State workflowStartState = WorkflowDiagramUtils.getWorkflowStartState(workflow); - modelConnections.add(new ModelConnection(WorkflowDiagramUtils.wfStart, workflowStartState.getName(), "")); - - List workflowStates = workflow.getStates(); - for(State state : workflowStates) { - if(state instanceof SwitchState) { - SwitchState switchState = (SwitchState) state; - if(switchState.getDataConditions() != null && switchState.getDataConditions().size() > 0) { - for(DataCondition dataCondition : switchState.getDataConditions()) { - - if(dataCondition.getTransition() != null) { - if(dataCondition.getTransition().getProduceEvents() != null && dataCondition.getTransition().getProduceEvents().size() > 0) { - List producedEvents = dataCondition.getTransition().getProduceEvents().stream() - .map(t -> t.getEventRef()) - .collect(Collectors.toList()); - - String desc = ""; - if(dataCondition.getName() != null && dataCondition.getName().trim().length() > 0) { - desc = dataCondition.getName(); - } - desc += " Produced Events: " + producedEvents.stream().collect(Collectors.joining(",")); - modelConnections.add(new ModelConnection(switchState.getName(), dataCondition.getTransition().getNextState(), desc)); - } else { - String desc = ""; - if(dataCondition.getName() != null && dataCondition.getName().trim().length() > 0) { - desc = dataCondition.getName(); - } - modelConnections.add(new ModelConnection(switchState.getName(), dataCondition.getTransition().getNextState(), desc)); - } - } - - if(dataCondition.getEnd() != null) { - if(dataCondition.getEnd().getProduceEvents() != null && dataCondition.getEnd().getProduceEvents().size() > 0) { - List producedEvents = dataCondition.getEnd().getProduceEvents().stream() - .map(t -> t.getEventRef()) - .collect(Collectors.toList()); - - String desc = ""; - if(dataCondition.getName() != null && dataCondition.getName().trim().length() > 0) { - desc = dataCondition.getName(); - } - desc += " Produced Events: " + producedEvents.stream().collect(Collectors.joining(",")); - modelConnections.add(new ModelConnection(switchState.getName(), WorkflowDiagramUtils.wfEnd, desc)); - } else { - String desc = ""; - if(dataCondition.getName() != null && dataCondition.getName().trim().length() > 0) { - desc = dataCondition.getName(); - } - modelConnections.add(new ModelConnection(switchState.getName(), WorkflowDiagramUtils.wfEnd, desc)); - } - } - - } - } - - if(switchState.getEventConditions() != null && switchState.getEventConditions().size() > 0) { - for(EventCondition eventCondition : switchState.getEventConditions()) { - - if(eventCondition.getTransition() != null) { - if(eventCondition.getTransition().getProduceEvents() != null && eventCondition.getTransition().getProduceEvents().size() > 0) { - List producedEvents = eventCondition.getTransition().getProduceEvents().stream() - .map(t -> t.getEventRef()) - .collect(Collectors.toList()); - - String desc = ""; - if(eventCondition.getName() != null && eventCondition.getName().trim().length() > 0) { - desc = eventCondition.getName(); - } - desc += " Produced Events: " + producedEvents.stream().collect(Collectors.joining(",")); - modelConnections.add(new ModelConnection(switchState.getName(), eventCondition.getTransition().getNextState(), desc)); - } else { - String desc = ""; - if(eventCondition.getName() != null && eventCondition.getName().trim().length() > 0) { - desc = eventCondition.getName(); - } - modelConnections.add(new ModelConnection(switchState.getName(), eventCondition.getTransition().getNextState(), desc)); - } - } - - if(eventCondition.getEnd() != null) { - if(eventCondition.getEnd().getProduceEvents() != null && eventCondition.getEnd().getProduceEvents().size() > 0) { - List producedEvents = eventCondition.getEnd().getProduceEvents().stream() - .map(t -> t.getEventRef()) - .collect(Collectors.toList()); - - String desc = ""; - if(eventCondition.getName() != null && eventCondition.getName().trim().length() > 0) { - desc = eventCondition.getName(); - } - desc += " Produced Events: " + producedEvents.stream().collect(Collectors.joining(",")); - modelConnections.add(new ModelConnection(switchState.getName(), WorkflowDiagramUtils.wfEnd, desc)); - } else { - String desc = ""; - if(eventCondition.getName() != null && eventCondition.getName().trim().length() > 0) { - desc = eventCondition.getName(); - } - modelConnections.add(new ModelConnection(switchState.getName(), WorkflowDiagramUtils.wfEnd, desc)); - } - } - - } - } - - // default - if(switchState.getDefault() != null) { - if(switchState.getDefault().getTransition() != null) { - if(switchState.getDefault().getTransition().getProduceEvents() != null && switchState.getDefault().getTransition().getProduceEvents().size() > 0) { - List producedEvents = switchState.getDefault().getTransition().getProduceEvents().stream() - .map(t -> t.getEventRef()) - .collect(Collectors.toList()); - - String desc = "default - "; - desc += " Produced Events: " + producedEvents.stream().collect(Collectors.joining(",")); - modelConnections.add(new ModelConnection(switchState.getName(), switchState.getDefault().getTransition().getNextState(), desc)); - } else { - String desc = "default"; - modelConnections.add(new ModelConnection(switchState.getName(), switchState.getDefault().getTransition().getNextState(), desc)); - } - } - - if(switchState.getDefault().getEnd() != null) { - if(switchState.getDefault().getEnd().getProduceEvents() != null && switchState.getDefault().getEnd().getProduceEvents().size() > 0) { - List producedEvents = switchState.getDefault().getEnd().getProduceEvents().stream() - .map(t -> t.getEventRef()) - .collect(Collectors.toList()); - - String desc = "default - "; - desc += " Produced Events: " + producedEvents.stream().collect(Collectors.joining(",")); - modelConnections.add(new ModelConnection(switchState.getName(), WorkflowDiagramUtils.wfEnd, desc)); - } else { - String desc = "default"; - modelConnections.add(new ModelConnection(switchState.getName(), WorkflowDiagramUtils.wfEnd, desc)); - } - } - } - } else { - if(state.getTransition() != null) { - if(state.getTransition().getProduceEvents() != null && state.getTransition().getProduceEvents().size() > 0) { - List producedEvents = state.getTransition().getProduceEvents().stream() - .map(t -> t.getEventRef()) - .collect(Collectors.toList()); - - String desc = "Produced Events: " + producedEvents.stream().collect(Collectors.joining(",")); - modelConnections.add(new ModelConnection(state.getName(), state.getTransition().getNextState(), desc)); - } else { - modelConnections.add(new ModelConnection(state.getName(), state.getTransition().getNextState(), "")); - } - } - - if(state.getEnd() != null) { - if(state.getEnd().getProduceEvents() != null && state.getEnd().getProduceEvents().size() > 0) { - List producedEvents = state.getEnd().getProduceEvents().stream() - .map(t -> t.getEventRef()) - .collect(Collectors.toList()); - - String desc = "Produced Events: " + producedEvents.stream().collect(Collectors.joining(",")); - modelConnections.add(new ModelConnection(state.getName(), WorkflowDiagramUtils.wfEnd, desc)); - } else { - modelConnections.add(new ModelConnection(state.getName(), WorkflowDiagramUtils.wfEnd, "")); - } - } - } - } - } - - private void inspectStatesInfo(Workflow workflow) { - List workflowStates = workflow.getStates(); - for(State state : workflowStates) { - ModelState modelState = new ModelState(state.getName()); - - if(state instanceof EventState) { - EventState eventState = (EventState) state; - - List events = eventState.getOnEvents().stream() - .flatMap(t -> t.getEventRefs().stream()) - .collect(Collectors.toList()); - - modelState.addInfo("Type: Event State"); - modelState.addInfo("Events: " + events.stream().collect(Collectors.joining(" "))); - - } - - if(state instanceof OperationState) { - OperationState operationState = (OperationState) state; - - modelState.addInfo("Type: Operation State"); - modelState.addInfo("Action mode: " + Optional.ofNullable(operationState.getActionMode()).orElse(OperationState.ActionMode.SEQUENTIAL)); - modelState.addInfo("Num. of actions: " + Optional.ofNullable(operationState.getActions().size()).orElse(0)); - } - - if(state instanceof SwitchState) { - SwitchState switchState = (SwitchState) state; - - - modelState.addInfo("Type: Switch State"); - if(switchState.getDataConditions() != null && switchState.getDataConditions().size() > 0) { - modelState.addInfo("Condition type: data-based"); - modelState.addInfo("Num. of conditions: " + switchState.getDataConditions().size()); - } - - if(switchState.getEventConditions() != null && switchState.getEventConditions().size() > 0) { - modelState.addInfo("Condition type: event-based"); - modelState.addInfo("Num. of conditions: " + switchState.getEventConditions().size()); - } - - if(switchState.getDefault() != null) { - if(switchState.getDefault().getTransition() != null) { - modelState.addInfo("Default to: " + switchState.getDefault().getTransition().getNextState()); - } - - if(switchState.getDefault().getEnd() != null) { - modelState.addInfo("Default to: End"); - } - } - - } - - if(state instanceof DelayState) { - DelayState delayState = (DelayState) state; - - modelState.addInfo("Type: Delay State"); - modelState.addInfo("Delay: " + delayState.getTimeDelay()); - } - - if(state instanceof ParallelState) { - ParallelState parallelState = (ParallelState) state; - - modelState.addInfo("Type: Parallel State"); - modelState.addInfo("Completion type: \"" + parallelState.getCompletionType().value() + "\""); - modelState.addInfo("Num. of branches: " + parallelState.getBranches().size()); - } - - if(state instanceof SubflowState) { - SubflowState subflowState = (SubflowState) state; - - modelState.addInfo("Type: SubFlow State"); - modelState.addInfo("Workflow ID: " + subflowState.getWorkflowId()); - } - - if(state instanceof InjectState) { - modelState.addInfo("Type: Inject State"); - } - - if(state instanceof ForEachState) { - ForEachState forEachState = (ForEachState) state; - - modelState.addInfo("Type: ForEach State"); - modelState.addInfo("Input collection: " + forEachState.getInputCollection()); - if(forEachState.getActions() != null && forEachState.getActions().size() > 0) { - modelState.addInfo("Num. of actions: " + forEachState.getActions().size()); - } - - if(forEachState.getWorkflowId() != null && forEachState.getWorkflowId().length() > 0) { - modelState.addInfo("Workflow ID: " + forEachState.getWorkflowId()); - } - } - - if(state instanceof CallbackState) { - CallbackState callbackState = (CallbackState) state; - - modelState.addInfo("Type: Callback State"); - modelState.addInfo("Callback function: " + callbackState.getAction().getFunctionRef().getRefName()); - modelState.addInfo("Callback event: " + callbackState.getEventRef()); - } - - modelStates.add(modelState); - } - } - - public Workflow getWorkflow() { - return workflow; - } - - public void setWorkflow(Workflow workflow) { - this.workflow = workflow; - } - - public String getTitle() { - return title; - } - - public void setTitle(String title) { - this.title = title; - } - - public String getLegend() { - return legend; - } - - public void setLegend(String legend) { - this.legend = legend; - } - - public String getFooter() { - return footer; - } - - public void setFooter(String footer) { - this.footer = footer; - } - - public List getModelStates() { - return modelStates; - } - - public void setModelStates(List modelStates) { - this.modelStates = modelStates; - } - - public List getModelConnections() { - return modelConnections; - } - - public void setModelConnections(List modelConnections) { - this.modelConnections = modelConnections; - } - - public List getModelStateDefs() { - return modelStateDefs; - } - - public void setModelStateDefs(List modelStateDefs) { - this.modelStateDefs = modelStateDefs; - } - - public boolean getShowLegend() { - return showLegend; - } -} diff --git a/diagram/src/main/java/io/serverlessworkflow/diagram/utils/WorkflowDiagramUtils.java b/diagram/src/main/java/io/serverlessworkflow/diagram/utils/WorkflowDiagramUtils.java deleted file mode 100644 index 9ff586c5..00000000 --- a/diagram/src/main/java/io/serverlessworkflow/diagram/utils/WorkflowDiagramUtils.java +++ /dev/null @@ -1,60 +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.diagram.utils; - -import io.serverlessworkflow.api.Workflow; -import io.serverlessworkflow.api.interfaces.State; -import io.serverlessworkflow.api.states.DefaultState; - -import java.util.List; -import java.util.stream.Collectors; - -public class WorkflowDiagramUtils { - public static final String versionSeparator = " v"; - public static final String wfStart = "wfstart"; - public static final String wfEnd = "wfend"; - public static final String startEnd = "[*]"; - public static final String connection = " --> "; - public static final String description = " : "; - public static final String title = "title "; - public static final String footer = "center footer Serverless Workflow Specification - serverlessworkflow.io"; - public static final String legendStart = new StringBuilder().append("legend top center").append(System.lineSeparator()).toString(); - public static final String legendEnd = new StringBuilder().append(System.lineSeparator()).append("endlegend").toString(); - public static final String stateDef = "state "; - public static final String stateAsName = " as "; - public static final String typeDefStart = " << "; - public static final String typeDefEnd = " >> "; - - - public static State getWorkflowStartState(Workflow workflow) { - return workflow.getStates().stream() - .filter(ws -> ws.getName().equals(workflow.getStart().getStateName())) - .findFirst().get(); - } - - public static List getStatesByType(Workflow workflow, DefaultState.Type type) { - return workflow.getStates().stream() - .filter(ws -> ws.getType() == type) - .collect(Collectors.toList()); - } - - public static List getWorkflowEndStates(Workflow workflow) { - return workflow.getStates().stream() - .filter(ws -> ws.getEnd() != null) - .collect(Collectors.toList()); - } -} diff --git a/diagram/src/main/java/io/serverlessworkflow/diagram/utils/WorkflowToPlantuml.java b/diagram/src/main/java/io/serverlessworkflow/diagram/utils/WorkflowToPlantuml.java deleted file mode 100644 index e46fa9d9..00000000 --- a/diagram/src/main/java/io/serverlessworkflow/diagram/utils/WorkflowToPlantuml.java +++ /dev/null @@ -1,33 +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.diagram.utils; - -import io.serverlessworkflow.api.Workflow; -import io.serverlessworkflow.diagram.config.ThymeleafConfig; -import io.serverlessworkflow.diagram.model.WorkflowDiagramModel; -import org.thymeleaf.TemplateEngine; -import org.thymeleaf.context.Context; - -public class WorkflowToPlantuml { - public static String convert(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); - } -} diff --git a/diagram/src/main/resources/META-INF/services/io.serverlessworkflow.api.interfaces.WorkflowDiagram b/diagram/src/main/resources/META-INF/services/io.serverlessworkflow.api.interfaces.WorkflowDiagram deleted file mode 100644 index 637a614f..00000000 --- a/diagram/src/main/resources/META-INF/services/io.serverlessworkflow.api.interfaces.WorkflowDiagram +++ /dev/null @@ -1 +0,0 @@ -io.serverlessworkflow.diagram.WorkflowDiagramImpl \ No newline at end of file diff --git a/diagram/src/main/resources/templates/plantuml/workflow-template.txt b/diagram/src/main/resources/templates/plantuml/workflow-template.txt deleted file mode 100644 index 9d8f8c0b..00000000 --- a/diagram/src/main/resources/templates/plantuml/workflow-template.txt +++ /dev/null @@ -1,47 +0,0 @@ -@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<< delay >> #b83b5e - BorderColor<< parallel >> #6a2c70 - BorderColor<< subflow >> #87753c - 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 | Delay | Parallel | SubFlow | Inject | ForEach | CallBack | -|<#7fe5f0>|<#bada55>|<#92a0f2>|<#b83b5e>|<#6a2c70>|<#87753c>|<#1e5f74>|<#931a25>|<#ffcb8e>| -endlegend -[/] - -@enduml \ No newline at end of file diff --git a/diagram/src/test/java/io/serverlessworkflow/diagram/test/WorkflowDiagramTest.java b/diagram/src/test/java/io/serverlessworkflow/diagram/test/WorkflowDiagramTest.java deleted file mode 100644 index 80bed886..00000000 --- a/diagram/src/test/java/io/serverlessworkflow/diagram/test/WorkflowDiagramTest.java +++ /dev/null @@ -1,70 +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.diagram.test; - -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; - -import static org.junit.jupiter.api.Assertions.assertNotNull; - -public class WorkflowDiagramTest { - - @ParameterizedTest - @ValueSource(strings = {"/examples/applicantrequest.json", "/examples/applicantrequest.yml", - "/examples/carauctionbids.json", "/examples/carauctionbids.yml", - "/examples/creditcheck.json", "/examples/creditcheck.yml", - "/examples/eventbasedgreeting.json", "/examples/eventbasedgreeting.yml", - "/examples/finalizecollegeapplication.json", "/examples/finalizecollegeapplication.yml", - "/examples/greeting.json", "/examples/greeting.yml", - "/examples/helloworld.json", "/examples/helloworld.yml", - "/examples/jobmonitoring.json", "/examples/jobmonitoring.yml", - "/examples/monitorpatient.json", "/examples/monitorpatient.yml", - "/examples/parallel.json", "/examples/parallel.yml", - "/examples/provisionorder.json", "/examples/provisionorder.yml", - "/examples/sendcloudevent.json", "/examples/sendcloudevent.yml", - "/examples/solvemathproblems.json", "/examples/solvemathproblems.yml", - "/examples/foreachstatewithactions.json", "/examples/foreachstatewithactions.yml", - "/examples/periodicinboxcheck.json", "/examples/periodicinboxcheck.yml", - "/examples/vetappointmentservice.json", "/examples/vetappointmentservice.yml", - "/examples/eventbasedtransition.json", "/examples/eventbasedtransition.yml", - "/examples/roomreadings.json", "/examples/roomreadings.yml", - "/examples/checkcarvitals.json", "/examples/checkcarvitals.yml", - "/examples/booklending.json", "/examples/booklending.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(); - workflowDiagram.setWorkflow(workflow); - - String diagramSVG = workflowDiagram.getSvgDiagram(); - - Assertions.assertNotNull(diagramSVG); - - } -} diff --git a/diagram/src/test/java/io/serverlessworkflow/diagram/test/utils/DiagramTestUtils.java b/diagram/src/test/java/io/serverlessworkflow/diagram/test/utils/DiagramTestUtils.java deleted file mode 100644 index 74f46c6a..00000000 --- a/diagram/src/test/java/io/serverlessworkflow/diagram/test/utils/DiagramTestUtils.java +++ /dev/null @@ -1,70 +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.diagram.test.utils; - -import io.serverlessworkflow.api.mapper.JsonObjectMapper; -import io.serverlessworkflow.api.mapper.YamlObjectMapper; - -import java.io.*; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -public class DiagramTestUtils { - private static JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(); - private static YamlObjectMapper yamlObjectMapper = new YamlObjectMapper(); - - public static final Path resourceDirectory = Paths.get("src", - "test", - "resources"); - public static final String absolutePath = resourceDirectory.toFile().getAbsolutePath(); - - public static Path getResourcePath(String file) { - return Paths.get(absolutePath + File.separator + file); - } - - public static InputStream getInputStreamFromPath(Path path) throws Exception { - return Files.newInputStream(path); - } - - public static String readWorkflowFile(String location) { - return readFileAsString(classpathResourceReader(location)); - } - - public static Reader classpathResourceReader(String location) { - return new InputStreamReader(DiagramTestUtils.class.getResourceAsStream(location)); - } - - public static String readFileAsString(Reader reader) { - try { - StringBuilder fileData = new StringBuilder(1000); - char[] buf = new char[1024]; - int numRead; - while ((numRead = reader.read(buf)) != -1) { - String readData = String.valueOf(buf, - 0, - numRead); - fileData.append(readData); - buf = new char[1024]; - } - reader.close(); - return fileData.toString(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/diagram/src/test/resources/examples/applicantrequest.json b/diagram/src/test/resources/examples/applicantrequest.json deleted file mode 100644 index b330bdeb..00000000 --- a/diagram/src/test/resources/examples/applicantrequest.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "id": "applicantrequest", - "version": "1.0", - "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" - } - ], - "default": { - "transition": "RejectApplication" - } - }, - { - "name": "StartApplication", - "type": "subflow", - "workflowId": "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/diagram/src/test/resources/examples/applicantrequest.yml b/diagram/src/test/resources/examples/applicantrequest.yml deleted file mode 100644 index d75d400f..00000000 --- a/diagram/src/test/resources/examples/applicantrequest.yml +++ /dev/null @@ -1,31 +0,0 @@ -id: applicantrequest -version: '1.0' -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 - default: - transition: RejectApplication - - name: StartApplication - type: subflow - workflowId: 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/diagram/src/test/resources/examples/booklending.json b/diagram/src/test/resources/examples/booklending.json deleted file mode 100644 index 37fbcf8b..00000000 --- a/diagram/src/test/resources/examples/booklending.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "id": "booklending", - "name": "Book Lending Workflow", - "version": "1.0", - "start": "Book Lending Request", - "states": [ - { - "name": "Book Lending Request", - "type": "event", - "onEvents": [ - { - "eventRefs": ["Book Lending Request Event"] - } - ], - "transition": "Get Book Status" - }, - { - "name": "Get Book Status", - "type": "operation", - "actions": [ - { - "functionRef": { - "refName": "Get status for book", - "arguments": { - "bookid": "${ .book.id }" - } - } - } - ], - "transition": "Book Status Decision" - }, - { - "name": "Book Status Decision", - "type": "switch", - "dataConditions": [ - { - "name": "Book is on loan", - "condition": "${ .book.status == \"onloan\" }", - "transition": "Report Status To Lender" - }, - { - "name": "Check is available", - "condition": "${ .book.status == \"available\" }", - "transition": "Check Out Book" - } - ] - }, - { - "name": "Report Status To Lender", - "type": "operation", - "actions": [ - { - "functionRef": { - "refName": "Send status to lender", - "arguments": { - "bookid": "${ .book.id }", - "message": "Book ${ .book.title } is already on loan" - } - } - } - ], - "transition": "Wait for Lender response" - }, - { - "name": "Wait for Lender response", - "type": "switch", - "eventConditions": [ - { - "name": "Hold Book", - "eventRef": "Hold Book Event", - "transition": "Request Hold" - }, - { - "name": "Decline Book Hold", - "eventRef": "Decline Hold Event", - "transition": "Cancel Request" - } - ] - }, - { - "name": "Request Hold", - "type": "operation", - "actions": [ - { - "functionRef": { - "refName": "Request hold for lender", - "arguments": { - "bookid": "${ .book.id }", - "lender": "${ .lender }" - } - } - } - ], - "transition": "Wait two weeks" - }, - { - "name": "Wait two weeks", - "type": "delay", - "timeDelay": "P2W", - "transition": "Get Book Status" - }, - { - "name": "Check Out Book", - "type": "operation", - "actions": [ - { - "functionRef": { - "refName": "Check out book with id", - "arguments": { - "bookid": "${ .book.id }" - } - } - }, - { - "functionRef": { - "refName": "Notify Lender for checkout", - "arguments": { - "bookid": "${ .book.id }", - "lender": "${ .lender }" - } - } - } - ], - "end": true - } - ], - "functions": [], - "events": [] -} \ No newline at end of file diff --git a/diagram/src/test/resources/examples/booklending.yml b/diagram/src/test/resources/examples/booklending.yml deleted file mode 100644 index a595588f..00000000 --- a/diagram/src/test/resources/examples/booklending.yml +++ /dev/null @@ -1,74 +0,0 @@ -id: booklending -name: Book Lending Workflow -version: '1.0' -start: Book Lending Request -states: - - name: Book Lending Request - type: event - onEvents: - - eventRefs: - - Book Lending Request Event - transition: Get Book Status - - name: Get Book Status - type: operation - actions: - - functionRef: - refName: Get status for book - arguments: - bookid: "${ .book.id }" - transition: Book Status Decision - - name: Book Status Decision - type: switch - dataConditions: - - name: Book is on loan - condition: ${ .book.status == "onloan" } - transition: Report Status To Lender - - name: Check is available - condition: ${ .book.status == "available" } - transition: Check Out Book - - name: Report Status To Lender - type: operation - actions: - - functionRef: - refName: Send status to lender - arguments: - bookid: "${ .book.id }" - message: Book ${ .book.title } is already on loan - transition: Wait for Lender response - - name: Wait for Lender response - type: switch - eventConditions: - - name: Hold Book - eventRef: Hold Book Event - transition: Request Hold - - name: Decline Book Hold - eventRef: Decline Hold Event - transition: Cancel Request - - name: Request Hold - type: operation - actions: - - functionRef: - refName: Request fold for lender - arguments: - bookid: "${ .book.id }" - lender: "${ .lender }" - transition: Wait two weeks - - name: Wait two weeks - type: delay - timeDelay: P2W - transition: Get Book Status - - name: Check Out Book - type: operation - actions: - - functionRef: - refName: Check out book with id - arguments: - bookid: "${ .book.id }" - - functionRef: - refName: Notify Lender for checkout - arguments: - bookid: "${ .book.id }" - lender: "${ .lender }" - end: true -functions: -events: \ No newline at end of file diff --git a/diagram/src/test/resources/examples/carauctionbids.json b/diagram/src/test/resources/examples/carauctionbids.json deleted file mode 100644 index 8c2fde11..00000000 --- a/diagram/src/test/resources/examples/carauctionbids.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "id": "handleCarAuctionBid", - "version": "1.0", - "name": "Car Auction Bidding Workflow", - "description": "Store a single bid whole the car auction is active", - "start": { - "stateName": "StoreCarAuctionBid", - "schedule": "2020-03-20T09:00:00Z/2020-03-20T15:00:00Z" - }, - "functions": [ - { - "name": "StoreBidFunction", - "operation": "http://myapis.org/carauctionapi.json#storeBid" - } - ], - "events": [ - { - "name": "CarBidEvent", - "type": "carBidMadeType", - "source": "carBidEventSource" - } - ], - "states": [ - { - "name": "StoreCarAuctionBid", - "type": "event", - "exclusive": true, - "onEvents": [ - { - "eventRefs": ["CarBidEvent"], - "actions": [{ - "functionRef": { - "refName": "StoreBidFunction", - "arguments": { - "bid": "${ .bid }" - } - } - }] - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/diagram/src/test/resources/examples/carauctionbids.yml b/diagram/src/test/resources/examples/carauctionbids.yml deleted file mode 100644 index f943f52f..00000000 --- a/diagram/src/test/resources/examples/carauctionbids.yml +++ /dev/null @@ -1,27 +0,0 @@ -id: handleCarAuctionBid -version: '1.0' -name: Car Auction Bidding Workflow -description: Store a single bid whole the car auction is active -start: - stateName: StoreCarAuctionBid - schedule: 2020-03-20T09:00:00Z/2020-03-20T15:00:00Z -functions: - - name: StoreBidFunction - operation: http://myapis.org/carauctionapi.json#storeBid -events: - - name: CarBidEvent - type: carBidMadeType - source: carBidEventSource -states: - - name: StoreCarAuctionBid - type: event - exclusive: true - onEvents: - - eventRefs: - - CarBidEvent - actions: - - functionRef: - refName: StoreBidFunction - arguments: - bid: "${ .bid }" - end: true \ No newline at end of file diff --git a/diagram/src/test/resources/examples/checkcarvitals.json b/diagram/src/test/resources/examples/checkcarvitals.json deleted file mode 100644 index 504235d2..00000000 --- a/diagram/src/test/resources/examples/checkcarvitals.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "id": "checkcarvitals", - "name": "Check Car Vitals Workflow", - "version": "1.0", - "start": "WhenCarIsOn", - "states": [ - { - "name": "WhenCarIsOn", - "type": "event", - "onEvents": [ - { - "eventRefs": ["CarTurnedOnEvent"] - } - ], - "transition": "DoCarVitalsChecks" - }, - { - "name": "DoCarVitalsChecks", - "type": "subflow", - "workflowId": "vitalscheck", - "repeat": { - "stopOnEvents": ["CarTurnedOffEvent"] - }, - "end": true - } - ], - "events": [ - { - "name": "CarTurnedOnEvent", - "type": "car.events", - "source": "my/car/start" - }, - { - "name": "CarTurnedOffEvent", - "type": "car.events", - "source": "my/car/start" - } - ] -} \ No newline at end of file diff --git a/diagram/src/test/resources/examples/checkcarvitals.yml b/diagram/src/test/resources/examples/checkcarvitals.yml deleted file mode 100644 index d17d73ce..00000000 --- a/diagram/src/test/resources/examples/checkcarvitals.yml +++ /dev/null @@ -1,25 +0,0 @@ -id: checkcarvitals -name: Check Car Vitals Workflow -version: '1.0' -start: WhenCarIsOn -states: - - name: WhenCarIsOn - type: event - onEvents: - - eventRefs: - - CarTurnedOnEvent - transition: DoCarVitalsChecks - - name: DoCarVitalsChecks - type: subflow - workflowId: vitalscheck - repeat: - stopOnEvents: - - CarTurnedOffEvent - end: true -events: - - name: CarTurnedOnEvent - type: car.events - source: my/car/start - - name: CarTurnedOffEvent - type: car.events - source: my/car/start \ No newline at end of file diff --git a/diagram/src/test/resources/examples/creditcheck.json b/diagram/src/test/resources/examples/creditcheck.json deleted file mode 100644 index e6bfd9b6..00000000 --- a/diagram/src/test/resources/examples/creditcheck.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "id": "customercreditcheck", - "version": "1.0", - "name": "Customer Credit Check Workflow", - "description": "Perform Customer Credit Check", - "start": "CheckCredit", - "functions": [ - { - "name": "creditCheckFunction", - "operation": "http://myapis.org/creditcheckapi.json#doCreditCheck" - }, - { - "name": "sendRejectionEmailFunction", - "operation": "http://myapis.org/creditcheckapi.json#rejectionEmail" - } - ], - "events": [ - { - "name": "CreditCheckCompletedEvent", - "type": "creditCheckCompleteType", - "source": "creditCheckSource", - "correlation": [ - { - "contextAttributeName": "customerId" - } - ] - } - ], - "states": [ - { - "name": "CheckCredit", - "type": "callback", - "action": { - "functionRef": { - "refName": "callCreditCheckMicroservice", - "arguments": { - "customer": "${ .customer }" - } - } - }, - "eventRef": "CreditCheckCompletedEvent", - "timeout": "PT15M", - "transition": "EvaluateDecision" - }, - { - "name": "EvaluateDecision", - "type": "switch", - "dataConditions": [ - { - "condition": "${ .creditCheck | .decision == \"Approved\" }", - "transition": "StartApplication'" - }, - { - "condition": "${ .creditCheck | .decision == \"Denied\" }", - "transition": "RejectApplication" - } - ], - "default": { - "transition": "RejectApplication" - } - }, - { - "name": "StartApplication", - "type": "subflow", - "workflowId": "startApplicationWorkflowId", - "end": true - }, - { - "name": "RejectApplication", - "type": "operation", - "actionMode": "sequential", - "actions": [ - { - "functionRef": { - "refName": "sendRejectionEmailFunction", - "arguments": { - "applicant": "${ .customer }" - } - } - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/diagram/src/test/resources/examples/creditcheck.yml b/diagram/src/test/resources/examples/creditcheck.yml deleted file mode 100644 index c4e5eb4a..00000000 --- a/diagram/src/test/resources/examples/creditcheck.yml +++ /dev/null @@ -1,49 +0,0 @@ -id: customercreditcheck -version: '1.0' -name: Customer Credit Check Workflow -description: Perform Customer Credit Check -start: CheckCredit -functions: - - name: creditCheckFunction - operation: http://myapis.org/creditcheckapi.json#doCreditCheck - - name: sendRejectionEmailFunction - operation: http://myapis.org/creditcheckapi.json#rejectionEmail -events: - - name: CreditCheckCompletedEvent - type: creditCheckCompleteType - source: creditCheckSource - correlation: - - contextAttributeName: customerId -states: - - name: CheckCredit - type: callback - action: - functionRef: - refName: callCreditCheckMicroservice - arguments: - customer: "${ .customer }" - eventRef: CreditCheckCompletedEvent - timeout: PT15M - transition: EvaluateDecision - - name: EvaluateDecision - type: switch - dataConditions: - - condition: "${ .creditCheck | .decision == \"Approved\" }" - transition: StartApplication - - condition: "${ .creditCheck | .decision == \"Denied\" }" - transition: RejectApplication - default: - transition: RejectApplication - - name: StartApplication - type: subflow - workflowId: startApplicationWorkflowId - end: true - - name: RejectApplication - type: operation - actionMode: sequential - actions: - - functionRef: - refName: sendRejectionEmailFunction - arguments: - applicant: "${ .customer }" - end: true \ No newline at end of file diff --git a/diagram/src/test/resources/examples/eventbasedgreeting.json b/diagram/src/test/resources/examples/eventbasedgreeting.json deleted file mode 100644 index aa80a3b4..00000000 --- a/diagram/src/test/resources/examples/eventbasedgreeting.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "id": "eventbasedgreeting", - "version": "1.0", - "name": "Event Based Greeting Workflow", - "description": "Event Based Greeting", - "start": "Greet", - "events": [ - { - "name": "GreetingEvent", - "type": "greetingEventType", - "source": "greetingEventSource" - } - ], - "functions": [ - { - "name": "greetingFunction", - "operation": "file://myapis/greetingapis.json#greeting" - } - ], - "states":[ - { - "name":"Greet", - "type":"event", - "onEvents": [{ - "eventRefs": ["GreetingEvent"], - "eventDataFilter": { - "data": "${ .data.greet }" - }, - "actions":[ - { - "functionRef": { - "refName": "greetingFunction", - "arguments": { - "name": "${ .greet.name }" - } - } - } - ] - }], - "stateDataFilter": { - "output": "${ .payload.greeting }" - }, - "end": true - } - ] -} \ No newline at end of file diff --git a/diagram/src/test/resources/examples/eventbasedgreeting.yml b/diagram/src/test/resources/examples/eventbasedgreeting.yml deleted file mode 100644 index ec22bec3..00000000 --- a/diagram/src/test/resources/examples/eventbasedgreeting.yml +++ /dev/null @@ -1,28 +0,0 @@ -id: eventbasedgreeting -version: '1.0' -name: Event Based Greeting Workflow -description: Event Based Greeting -start: Greet -events: - - name: GreetingEvent - type: greetingEventType - source: greetingEventSource -functions: - - name: greetingFunction - operation: file://myapis/greetingapis.json#greeting -states: - - name: Greet - type: event - onEvents: - - eventRefs: - - GreetingEvent - eventDataFilter: - data: "${ .data.greet }" - actions: - - functionRef: - refName: greetingFunction - arguments: - name: "${ .greet.name }" - stateDataFilter: - output: "${ .payload.greeting }" - end: true \ No newline at end of file diff --git a/diagram/src/test/resources/examples/eventbasedtransition.json b/diagram/src/test/resources/examples/eventbasedtransition.json deleted file mode 100644 index 29ee0271..00000000 --- a/diagram/src/test/resources/examples/eventbasedtransition.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "id": "eventbasedswitch", - "version": "1.0", - "name": "Event Based Switch Transitions", - "description": "Event Based Switch Transitions", - "start": "CheckVisaStatus", - "events": [ - { - "name": "visaApprovedEvent", - "type": "VisaApproved", - "source": "visaCheckSource" - }, - { - "name": "visaRejectedEvent", - "type": "VisaRejected", - "source": "visaCheckSource" - } - ], - "states":[ - { - "name":"CheckVisaStatus", - "type":"switch", - "eventConditions": [ - { - "eventRef": "visaApprovedEvent", - "transition": "HandleApprovedVisa" - }, - { - "eventRef": "visaRejectedEvent", - "transition": "HandleRejectedVisa" - } - ], - "eventTimeout": "PT1H", - "default": { - "transition": "HandleNoVisaDecision" - } - }, - { - "name": "HandleApprovedVisa", - "type": "subflow", - "workflowId": "handleApprovedVisaWorkflowID", - "end": true - }, - { - "name": "HandleRejectedVisa", - "type": "subflow", - "workflowId": "handleRejectedVisaWorkflowID", - "end": true - }, - { - "name": "HandleNoVisaDecision", - "type": "subflow", - "workflowId": "handleNoVisaDecisionWorkfowId", - "end": true - } - ] -} \ No newline at end of file diff --git a/diagram/src/test/resources/examples/eventbasedtransition.yml b/diagram/src/test/resources/examples/eventbasedtransition.yml deleted file mode 100644 index c00eef5d..00000000 --- a/diagram/src/test/resources/examples/eventbasedtransition.yml +++ /dev/null @@ -1,35 +0,0 @@ -id: eventbasedswitch -version: '1.0' -name: Event Based Switch Transitions -description: Event Based Switch Transitions -start: CheckVisaStatus -events: - - name: visaApprovedEvent - type: VisaApproved - source: visaCheckSource - - name: visaRejectedEvent - type: VisaRejected - source: visaCheckSource -states: - - name: CheckVisaStatus - type: switch - eventConditions: - - eventRef: visaApprovedEvent - transition: HandleApprovedVisa - - eventRef: visaRejectedEvent - transition: HandleRejectedVisa - eventTimeout: PT1H - default: - transition: HandleNoVisaDecision - - name: HandleApprovedVisa - type: subflow - workflowId: handleApprovedVisaWorkflowID - end: true - - name: HandleRejectedVisa - type: subflow - workflowId: handleRejectedVisaWorkflowID - end: true - - name: HandleNoVisaDecision - type: subflow - workflowId: handleNoVisaDecisionWorkfowId - end: true \ No newline at end of file diff --git a/diagram/src/test/resources/examples/finalizecollegeapplication.json b/diagram/src/test/resources/examples/finalizecollegeapplication.json deleted file mode 100644 index 4df99b15..00000000 --- a/diagram/src/test/resources/examples/finalizecollegeapplication.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "id": "finalizeCollegeApplication", - "name": "Finalize College Application", - "version": "1.0", - "start": "FinalizeApplication", - "events": [ - { - "name": "ApplicationSubmitted", - "type": "org.application.submitted", - "source": "applicationsource", - "correlation": [ - { - "contextAttributeName": "applicantId" - } - ] - }, - { - "name": "SATScoresReceived", - "type": "org.application.satscores", - "source": "applicationsource", - "correlation": [ - { - "contextAttributeName": "applicantId" - } - ] - }, - { - "name": "RecommendationLetterReceived", - "type": "org.application.recommendationLetter", - "source": "applicationsource", - "correlation": [ - { - "contextAttributeName": "applicantId" - } - ] - } - ], - "functions": [ - { - "name": "finalizeApplicationFunction", - "operation": "http://myapis.org/collegeapplicationapi.json#finalize" - } - ], - "states": [ - { - "name": "FinalizeApplication", - "type": "event", - "exclusive": false, - "onEvents": [ - { - "eventRefs": [ - "ApplicationSubmitted", - "SATScoresReceived", - "RecommendationLetterReceived" - ], - "actions": [ - { - "functionRef": { - "refName": "finalizeApplicationFunction", - "arguments": { - "student": "${ .applicantId }" - } - } - } - ] - } - ], - "end": { - "terminate": true - } - } - ] -} \ No newline at end of file diff --git a/diagram/src/test/resources/examples/finalizecollegeapplication.yml b/diagram/src/test/resources/examples/finalizecollegeapplication.yml deleted file mode 100644 index d84690ca..00000000 --- a/diagram/src/test/resources/examples/finalizecollegeapplication.yml +++ /dev/null @@ -1,39 +0,0 @@ -id: finalizeCollegeApplication -name: Finalize College Application -version: '1.0' -start: FinalizeApplication -events: - - name: ApplicationSubmitted - type: org.application.submitted - source: applicationsource - correlation: - - contextAttributeName: applicantId - - name: SATScoresReceived - type: org.application.satscores - source: applicationsource - correlation: - - contextAttributeName: applicantId - - name: RecommendationLetterReceived - type: org.application.recommendationLetter - source: applicationsource - correlation: - - contextAttributeName: applicantId -functions: - - name: finalizeApplicationFunction - operation: http://myapis.org/collegeapplicationapi.json#finalize -states: - - name: FinalizeApplication - type: event - exclusive: false - onEvents: - - eventRefs: - - ApplicationSubmitted - - SATScoresReceived - - RecommendationLetterReceived - actions: - - functionRef: - refName: finalizeApplicationFunction - arguments: - student: "${ .applicantId }" - end: - terminate: true \ No newline at end of file diff --git a/diagram/src/test/resources/examples/foreachstatewithactions.json b/diagram/src/test/resources/examples/foreachstatewithactions.json deleted file mode 100644 index 1d3dc1eb..00000000 --- a/diagram/src/test/resources/examples/foreachstatewithactions.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "id": "foreachstatewithactions", - "name": "ForEach State With Actions", - "description": "ForEach State With Actions", - "version": "1.0", - "start": "SendConfirmationForEachCompletedhOrder", - "functions": [ - { - "name": "sendConfirmationFunction", - "operation": "http://myapis.org/confirmationapi.json#sendConfirmation" - } - ], - "states": [ - { - "name":"SendConfirmationForEachCompletedhOrder", - "type":"foreach", - "inputCollection": "${ .orders[?(@.completed == true)] }", - "iterationParam": "${ .completedorder }", - "actions":[ - { - "functionRef": { - "refName": "sendConfirmationFunction", - "arguments": { - "orderNumber": "${ .completedorder.orderNumber }", - "email": "${ .completedorder.email }" - } - } - }], - "end": true - } - ] -} \ No newline at end of file diff --git a/diagram/src/test/resources/examples/foreachstatewithactions.yml b/diagram/src/test/resources/examples/foreachstatewithactions.yml deleted file mode 100644 index cead8472..00000000 --- a/diagram/src/test/resources/examples/foreachstatewithactions.yml +++ /dev/null @@ -1,20 +0,0 @@ -id: foreachstatewithactions -name: ForEach State With Actions -description: ForEach State With Actions -version: '1.0' -start: SendConfirmationForEachCompletedhOrder -functions: - - name: sendConfirmationFunction - operation: http://myapis.org/confirmationapi.json#sendConfirmation -states: - - name: SendConfirmationForEachCompletedhOrder - type: foreach - inputCollection: "${ .orders[?(@.completed == true)] }" - iterationParam: "${ .completedorder }" - actions: - - functionRef: - refName: sendConfirmationFunction - arguments: - orderNumber: "${ .completedorder.orderNumber }" - email: "${ .completedorder.email }" - end: true diff --git a/diagram/src/test/resources/examples/greeting.json b/diagram/src/test/resources/examples/greeting.json deleted file mode 100644 index 3540cb6d..00000000 --- a/diagram/src/test/resources/examples/greeting.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "id": "greeting", - "version": "1.0", - "name": "Greeting Workflow", - "description": "Greet Someone", - "start": "Greet", - "functions": [ - { - "name": "greetingFunction", - "operation": "file://myapis/greetingapis.json#greeting" - } - ], - "states":[ - { - "name":"Greet", - "type":"operation", - "actions":[ - { - "functionRef": { - "refName": "greetingFunction", - "arguments": { - "name": "${ .person.name }" - } - }, - "actionDataFilter": { - "results": "${ .greeting }" - } - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/diagram/src/test/resources/examples/greeting.yml b/diagram/src/test/resources/examples/greeting.yml deleted file mode 100644 index 8bbc8b4d..00000000 --- a/diagram/src/test/resources/examples/greeting.yml +++ /dev/null @@ -1,19 +0,0 @@ -id: greeting -version: '1.0' -name: Greeting Workflow -description: Greet Someone -start: Greet -functions: - - name: greetingFunction - operation: file://myapis/greetingapis.json#greeting -states: - - name: Greet - type: operation - actions: - - functionRef: - refName: greetingFunction - arguments: - name: "${ .person.name }" - actionDataFilter: - results: "${ .greeting }" - end: true \ No newline at end of file diff --git a/diagram/src/test/resources/examples/helloworld.json b/diagram/src/test/resources/examples/helloworld.json deleted file mode 100644 index c1864a6d..00000000 --- a/diagram/src/test/resources/examples/helloworld.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "helloworld", - "version": "1.0", - "name": "Hello World Workflow", - "description": "Inject Hello World", - "start": "Hello State", - "states":[ - { - "name":"Hello State", - "type":"inject", - "data": { - "result": "Hello World!" - }, - "end": true - } - ] -} \ No newline at end of file diff --git a/diagram/src/test/resources/examples/helloworld.yml b/diagram/src/test/resources/examples/helloworld.yml deleted file mode 100644 index 0d537603..00000000 --- a/diagram/src/test/resources/examples/helloworld.yml +++ /dev/null @@ -1,11 +0,0 @@ -id: helloworld -version: '1.0' -name: Hello World Workflow -description: Inject Hello World -start: Hello State -states: - - name: Hello State - type: inject - data: - result: Hello World! - end: true \ No newline at end of file diff --git a/diagram/src/test/resources/examples/jobmonitoring.json b/diagram/src/test/resources/examples/jobmonitoring.json deleted file mode 100644 index fda8446b..00000000 --- a/diagram/src/test/resources/examples/jobmonitoring.json +++ /dev/null @@ -1,138 +0,0 @@ -{ - "id": "jobmonitoring", - "version": "1.0", - "name": "Job Monitoring", - "description": "Monitor finished execution of a submitted job", - "start": "SubmitJob", - "functions": [ - { - "name": "submitJob", - "operation": "http://myapis.org/monitorapi.json#doSubmit" - }, - { - "name": "checkJobStatus", - "operation": "http://myapis.org/monitorapi.json#checkStatus" - }, - { - "name": "reportJobSuceeded", - "operation": "http://myapis.org/monitorapi.json#reportSucceeded" - }, - { - "name": "reportJobFailed", - "operation": "http://myapis.org/monitorapi.json#reportFailure" - } - ], - "states":[ - { - "name":"SubmitJob", - "type":"operation", - "actionMode":"sequential", - "actions":[ - { - "functionRef": { - "refName": "submitJob", - "arguments": { - "name": "${ .job.name }" - } - }, - "actionDataFilter": { - "results": "${ .jobuid }" - } - } - ], - "onErrors": [ - { - "error": "*", - "transition": "SubmitError" - } - ], - "stateDataFilter": { - "output": "${ .jobuid }" - }, - "transition": "WaitForCompletion'" - }, - { - "name": "SubmitError", - "type": "subflow", - "workflowId": "handleJobSubmissionErrorWorkflow", - "end": true - }, - { - "name": "WaitForCompletion", - "type": "delay", - "timeDelay": "PT5S", - "transition": "GetJobStatus" - }, - { - "name":"GetJobStatus", - "type":"operation", - "actionMode":"sequential", - "actions":[ - { - "functionRef": { - "refName": "checkJobStatus", - "arguments": { - "name": "${ .jobuid }" - } - }, - "actionDataFilter": { - "results": "${ .jobstatus }" - } - } - ], - "stateDataFilter": { - "output": "${ .jobstatus }" - }, - "transition": "DetermineCompletion" - }, - { - "name":"DetermineCompletion", - "type":"switch", - "dataConditions": [ - { - "condition": "${ .jobStatus == \"SUCCEEDED\" }", - "transition": "JobSucceeded" - }, - { - "condition": "${ .jobStatus == \"FAILED\" }", - "transition": "JobFailed" - } - ], - "default": { - "transition": "WaitForCompletion" - } - }, - { - "name":"JobSucceeded", - "type":"operation", - "actionMode":"sequential", - "actions":[ - { - "functionRef": { - "refName": "reportJobSuceeded", - "arguments": { - "name": "${ .jobuid }" - } - } - } - ], - "end": true - }, - { - "name":"JobFailed", - "type":"operation", - "actionMode":"sequential", - "actions":[ - { - "functionRef": { - "refName": "reportJobFailed", - "arguments": { - "name": "${ .jobuid }" - } - } - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/diagram/src/test/resources/examples/jobmonitoring.yml b/diagram/src/test/resources/examples/jobmonitoring.yml deleted file mode 100644 index 5f30b08e..00000000 --- a/diagram/src/test/resources/examples/jobmonitoring.yml +++ /dev/null @@ -1,79 +0,0 @@ -id: jobmonitoring -version: '1.0' -name: Job Monitoring -description: Monitor finished execution of a submitted job -start: SubmitJob -functions: - - name: submitJob - operation: http://myapis.org/monitorapi.json#doSubmit - - name: checkJobStatus - operation: http://myapis.org/monitorapi.json#checkStatus - - name: reportJobSuceeded - operation: http://myapis.org/monitorapi.json#reportSucceeded - - name: reportJobFailed - operation: http://myapis.org/monitorapi.json#reportFailure -states: - - name: SubmitJob - type: operation - actionMode: sequential - actions: - - functionRef: - refName: submitJob - arguments: - name: "${ .job.name }" - actionDataFilter: - results: "${ .jobuid }" - onErrors: - - error: "*" - transition: SubmitError - stateDataFilter: - output: "${ .jobuid }" - transition: WaitForCompletion - - name: SubmitError - type: subflow - workflowId: handleJobSubmissionErrorWorkflow - end: true - - name: WaitForCompletion - type: delay - timeDelay: PT5S - transition: GetJobStatus - - name: GetJobStatus - type: operation - actionMode: sequential - actions: - - functionRef: - refName: checkJobStatus - arguments: - name: "${ .jobuid }" - actionDataFilter: - results: "${ .jobstatus }" - stateDataFilter: - output: "${ .jobstatus }" - transition: DetermineCompletion - - name: DetermineCompletion - type: switch - dataConditions: - - condition: "${ .jobstatus == \"SUCCEEDED\" }" - transition: JobSucceeded - - condition: "${ .jobstatus == \"FAILED\" }" - transition: JobFailed - default: - transition: WaitForCompletion - - name: JobSucceeded - type: operation - actionMode: sequential - actions: - - functionRef: - refName: reportJobSuceeded - arguments: - name: "${ .jobuid }" - end: true - - name: JobFailed - type: operation - actionMode: sequential - actions: - - functionRef: - refName: reportJobFailed - arguments: - name: "${ .jobuid }" - end: true \ No newline at end of file diff --git a/diagram/src/test/resources/examples/monitorpatient.json b/diagram/src/test/resources/examples/monitorpatient.json deleted file mode 100644 index ab216b60..00000000 --- a/diagram/src/test/resources/examples/monitorpatient.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "id": "patientVitalsWorkflow", - "name": "Monitor Patient Vitals", - "version": "1.0", - "start": "MonitorVitals", - "events": [ - { - "name": "HighBodyTemperature", - "type": "org.monitor.highBodyTemp", - "source": "monitoringSource", - "correlation": [ - { - "contextAttributeName": "patientId" - } - ] - }, - { - "name": "HighBloodPressure", - "type": "org.monitor.highBloodPressure", - "source": "monitoringSource", - "correlation": [ - { - "contextAttributeName": "patientId" - } - ] - }, - { - "name": "HighRespirationRate", - "type": "org.monitor.highRespirationRate", - "source": "monitoringSource", - "correlation": [ - { - "contextAttributeName": "patientId" - } - ] - } - ], - "functions": [ - { - "name": "callPulmonologist", - "operation": "http://myapis.org/patientapis.json#callPulmonologist" - }, - { - "name": "sendTylenolOrder", - "operation": "http://myapis.org/patientapis.json#tylenolOrder" - }, - { - "name": "callNurse", - "operation": "http://myapis.org/patientapis.json#callNurse" - } - ], - "states": [ - { - "name": "MonitorVitals", - "type": "event", - "exclusive": true, - "onEvents": [{ - "eventRefs": ["HighBodyTemperature"], - "actions": [{ - "functionRef": { - "refName": "sendTylenolOrder", - "arguments": { - "patientid": "${ .patientId }" - } - } - }] - }, - { - "eventRefs": ["HighBloodPressure"], - "actions": [{ - "functionRef": { - "refName": "callNurse", - "arguments": { - "patientid": "${ .patientId }" - } - } - }] - }, - { - "eventRefs": ["HighRespirationRate"], - "actions": [{ - "functionRef": { - "refName": "callPulmonologist", - "arguments": { - "patientid": "${ .patientId }" - } - } - }] - } - ], - "end": { - "terminate": true - } - }] -} \ No newline at end of file diff --git a/diagram/src/test/resources/examples/monitorpatient.yml b/diagram/src/test/resources/examples/monitorpatient.yml deleted file mode 100644 index 33b99459..00000000 --- a/diagram/src/test/resources/examples/monitorpatient.yml +++ /dev/null @@ -1,55 +0,0 @@ -id: patientVitalsWorkflow -name: Monitor Patient Vitals -version: '1.0' -start: MonitorVitals -events: - - name: HighBodyTemperature - type: org.monitor.highBodyTemp - source: monitoringSource - correlation: - - contextAttributeName: patientId - - name: HighBloodPressure - type: org.monitor.highBloodPressure - source: monitoringSource - correlation: - - contextAttributeName: patientId - - name: HighRespirationRate - type: org.monitor.highRespirationRate - source: monitoringSource - correlation: - - contextAttributeName: patientId -functions: - - name: callPulmonologist - operation: http://myapis.org/patientapis.json#callPulmonologist - - name: sendTylenolOrder - operation: http://myapis.org/patientapis.json#tylenolOrder - - name: callNurse - operation: http://myapis.org/patientapis.json#callNurse -states: - - name: MonitorVitals - type: event - exclusive: true - onEvents: - - eventRefs: - - HighBodyTemperature - actions: - - functionRef: - refName: sendTylenolOrder - arguments: - patientid: "${ .patientId }" - - eventRefs: - - HighBloodPressure - actions: - - functionRef: - refName: callNurse - arguments: - patientid: "${ .patientId }" - - eventRefs: - - HighRespirationRate - actions: - - functionRef: - refName: callPulmonologist - arguments: - patientid: "${ .patientId }" - end: - terminate: true diff --git a/diagram/src/test/resources/examples/parallel.json b/diagram/src/test/resources/examples/parallel.json deleted file mode 100644 index cb50c8fc..00000000 --- a/diagram/src/test/resources/examples/parallel.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "id": "parallelexec", - "version": "1.0", - "name": "Parallel Execution Workflow", - "description": "Executes two branches in parallel", - "start": "ParallelExec", - "states":[ - { - "name": "ParallelExec", - "type": "parallel", - "completionType": "and", - "branches": [ - { - "name": "ShortDelayBranch", - "workflowId": "shortdelayworkflowid" - }, - { - "name": "LongDelayBranch", - "workflowId": "longdelayworkflowid" - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/diagram/src/test/resources/examples/parallel.yml b/diagram/src/test/resources/examples/parallel.yml deleted file mode 100644 index 49536973..00000000 --- a/diagram/src/test/resources/examples/parallel.yml +++ /dev/null @@ -1,15 +0,0 @@ -id: parallelexec -version: '1.0' -name: Parallel Execution Workflow -description: Executes two branches in parallel -start: ParallelExec -states: - - name: ParallelExec - type: parallel - completionType: and - branches: - - name: ShortDelayBranch - workflowId: shortdelayworkflowid - - name: LongDelayBranch - workflowId: longdelayworkflowid - end: true \ No newline at end of file diff --git a/diagram/src/test/resources/examples/periodicinboxcheck.json b/diagram/src/test/resources/examples/periodicinboxcheck.json deleted file mode 100644 index 5e20c30e..00000000 --- a/diagram/src/test/resources/examples/periodicinboxcheck.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "id": "checkInbox", - "name": "Check Inbox Workflow", - "description": "Periodically Check Inbox", - "start": { - "stateName": "CheckInbox", - "schedule": { - "cron": "0 0/15 * * * ?" - } - }, - "version": "1.0", - "functions": [ - { - "name": "checkInboxFunction", - "operation": "http://myapis.org/inboxapi.json#checkNewMessages" - }, - { - "name": "sendTextFunction", - "operation": "http://myapis.org/inboxapi.json#sendText" - } - ], - "states": [ - { - "name": "CheckInbox", - "type": "operation", - "actionMode": "sequential", - "actions": [ - { - "functionRef": "checkInboxFunction" - } - ], - "transition": "SendTextForHighPriority" - }, - { - "name": "SendTextForHighPriority", - "type": "foreach", - "inputCollection": "${ .messages }", - "iterationParam": "singlemessage", - "actions": [ - { - "functionRef": { - "refName": "sendTextFunction", - "arguments": { - "message": "${ .singlemessage }" - } - } - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/diagram/src/test/resources/examples/periodicinboxcheck.yml b/diagram/src/test/resources/examples/periodicinboxcheck.yml deleted file mode 100644 index 1d7de0bb..00000000 --- a/diagram/src/test/resources/examples/periodicinboxcheck.yml +++ /dev/null @@ -1,30 +0,0 @@ -id: checkInbox -name: Check Inbox Workflow -description: Periodically Check Inbox -start: - stateName: CheckInbox - schedule: - cron: 0 0/15 * * * ? -version: '1.0' -functions: - - name: checkInboxFunction - operation: http://myapis.org/inboxapi.json#checkNewMessages - - name: sendTextFunction - operation: http://myapis.org/inboxapi.json#sendText -states: - - name: CheckInbox - type: operation - actionMode: sequential - actions: - - functionRef: checkInboxFunction - transition: SendTextForHighPriority - - name: SendTextForHighPriority - type: foreach - inputCollection: "${ .messages }" - iterationParam: singlemessage - actions: - - functionRef: - refName: sendTextFunction - arguments: - message: "${ .singlemessage }" - end: true \ No newline at end of file diff --git a/diagram/src/test/resources/examples/provisionorder.json b/diagram/src/test/resources/examples/provisionorder.json deleted file mode 100644 index 1fb59102..00000000 --- a/diagram/src/test/resources/examples/provisionorder.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "id": "provisionorders", - "version": "1.0", - "name": "Provision Orders", - "description": "Provision Orders and handle errors thrown", - "start": "ProvisionOrder", - "functions": [ - { - "name": "provisionOrderFunction", - "operation": "http://myapis.org/provisioningapi.json#doProvision" - } - ], - "states":[ - { - "name":"ProvisionOrder", - "type":"operation", - "actionMode":"sequential", - "actions":[ - { - "functionRef": { - "refName": "provisionOrderFunction", - "arguments": { - "order": "${ .order }" - } - } - } - ], - "stateDataFilter": { - "output": "${ .exceptions }" - }, - "transition": "ApplyOrder", - "onErrors": [ - { - "error": "Missing order id", - "transition": "MissingId" - }, - { - "error": "Missing order item", - "transition": "MissingItem" - }, - { - "error": "Missing order quantity", - "transition": "MissingQuantity" - } - ] - }, - { - "name": "MissingId", - "type": "subflow", - "workflowId": "handleMissingIdExceptionWorkflow", - "end": true - }, - { - "name": "MissingItem", - "type": "subflow", - "workflowId": "handleMissingItemExceptionWorkflow", - "end": true - }, - { - "name": "MissingQuantity", - "type": "subflow", - "workflowId": "handleMissingQuantityExceptionWorkflow", - "end": true - }, - { - "name": "ApplyOrder", - "type": "subflow", - "workflowId": "applyOrderWorkflowId", - "end": true - } - ] -} \ No newline at end of file diff --git a/diagram/src/test/resources/examples/provisionorder.yml b/diagram/src/test/resources/examples/provisionorder.yml deleted file mode 100644 index b8eff2be..00000000 --- a/diagram/src/test/resources/examples/provisionorder.yml +++ /dev/null @@ -1,43 +0,0 @@ -id: provisionorders -version: '1.0' -name: Provision Orders -description: Provision Orders and handle errors thrown -start: ProvisionOrder -functions: - - name: provisionOrderFunction - operation: http://myapis.org/provisioningapi.json#doProvision -states: - - name: ProvisionOrder - type: operation - actionMode: sequential - actions: - - functionRef: - refName: provisionOrderFunction - arguments: - order: "${ .order }" - stateDataFilter: - output: "${ .exceptions }" - transition: ApplyOrder - onErrors: - - error: Missing order id - transition: MissingId - - error: Missing order item - transition: MissingItem - - error: Missing order quantity - transition: MissingQuantity - - name: MissingId - type: subflow - workflowId: handleMissingIdExceptionWorkflow - end: true - - name: MissingItem - type: subflow - workflowId: handleMissingItemExceptionWorkflow - end: true - - name: MissingQuantity - type: subflow - workflowId: handleMissingQuantityExceptionWorkflow - end: true - - name: ApplyOrder - type: subflow - workflowId: applyOrderWorkflowId - end: true \ No newline at end of file diff --git a/diagram/src/test/resources/examples/roomreadings.json b/diagram/src/test/resources/examples/roomreadings.json deleted file mode 100644 index aab4f25d..00000000 --- a/diagram/src/test/resources/examples/roomreadings.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "id": "roomreadings", - "name": "Room Temp and Humidity Workflow", - "version": "1.0", - "start": "ConsumeReading", - "execTimeout": { - "duration": "PT1H", - "runBefore": "GenerateReport" - }, - "keepActive": true, - "states": [ - { - "name": "ConsumeReading", - "type": "event", - "onEvents": [ - { - "eventRefs": ["TemperatureEvent", "HumidityEvent"], - "actions": [ - { - "functionRef": { - "refName": "LogReading" - } - } - ], - "eventDataFilter": { - "data": "${ .readings }" - } - } - ], - "end": true - }, - { - "name": "GenerateReport", - "type": "operation", - "actions": [ - { - "functionRef": { - "refName": "ProduceReport", - "arguments": { - "data": "${ .readings }" - } - } - } - ], - "end": { - "terminate": true - } - } - ], - "events": [ - { - "name": "TemperatureEvent", - "type": "my.home.sensors", - "source": "/home/rooms/+", - "correlation": [ - { - "contextAttributeName": "roomId" - } - ] - }, - { - "name": "HumidityEvent", - "type": "my.home.sensors", - "source": "/home/rooms/+", - "correlation": [ - { - "contextAttributeName": "roomId" - } - ] - } - ], - "functions": [ - { - "name": "LogReading", - "operation": "http.myorg.io/ordersservices.json#logreading" - }, - { - "name": "ProduceReport", - "operation": "http.myorg.io/ordersservices.json#produceReport" - } - ] -} \ No newline at end of file diff --git a/diagram/src/test/resources/examples/roomreadings.yml b/diagram/src/test/resources/examples/roomreadings.yml deleted file mode 100644 index cfbf2e82..00000000 --- a/diagram/src/test/resources/examples/roomreadings.yml +++ /dev/null @@ -1,46 +0,0 @@ -id: roomreadings -name: Room Temp and Humidity Workflow -version: '1.0' -start: ConsumeReading -execTimeout: - duration: PT1H - runBefore: GenerateReport -keepActive: true -states: - - name: ConsumeReading - type: event - onEvents: - - eventRefs: - - TemperatureEvent - - HumidityEvent - actions: - - functionRef: - refName: LogReading - eventDataFilter: - data: "${ .readings }" - end: true - - name: GenerateReport - type: operation - actions: - - functionRef: - refName: ProduceReport - arguments: - data: "${ .readings }" - end: - terminate: true -events: - - name: TemperatureEvent - type: my.home.sensors - source: "/home/rooms/+" - correlation: - - contextAttributeName: roomId - - name: HumidityEvent - type: my.home.sensors - source: "/home/rooms/+" - correlation: - - contextAttributeName: roomId -functions: - - name: LogReading - operation: http.myorg.io/ordersservices.json#logreading - - name: ProduceReport - operation: http.myorg.io/ordersservices.json#produceReport \ No newline at end of file diff --git a/diagram/src/test/resources/examples/sendcloudevent.json b/diagram/src/test/resources/examples/sendcloudevent.json deleted file mode 100644 index 5b94fc11..00000000 --- a/diagram/src/test/resources/examples/sendcloudevent.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "id": "sendcloudeventonprovision", - "version": "1.0", - "name": "Send CloudEvent on provision completion", - "start": "ProvisionOrdersState", - "events": [ - { - "name": "provisioningCompleteEvent", - "type": "provisionCompleteType", - "kind": "produced" - } - ], - "functions": [ - { - "name": "provisionOrderFunction", - "operation": "http://myapis.org/provisioning.json#doProvision" - } - ], - "states": [ - { - "name": "ProvisionOrdersState", - "type": "foreach", - "inputCollection": "${ .orders }", - "iterationParam": "singleorder", - "outputCollection": "${ .provisionedOrders }", - "actions": [ - { - "functionRef": { - "refName": "provisionOrderFunction", - "arguments": { - "order": "${ .singleorder }" - } - } - } - ], - "end": { - "produceEvents": [{ - "eventRef": "provisioningCompleteEvent", - "data": "${ .provisionedOrders }" - }] - } - } - ] -} \ No newline at end of file diff --git a/diagram/src/test/resources/examples/sendcloudevent.yml b/diagram/src/test/resources/examples/sendcloudevent.yml deleted file mode 100644 index 1359fb10..00000000 --- a/diagram/src/test/resources/examples/sendcloudevent.yml +++ /dev/null @@ -1,26 +0,0 @@ -id: sendcloudeventonprovision -version: '1.0' -name: Send CloudEvent on provision completion -start: ProvisionOrdersState -events: - - name: provisioningCompleteEvent - type: provisionCompleteType - kind: produced -functions: - - name: provisionOrderFunction - operation: http://myapis.org/provisioning.json#doProvision -states: - - name: ProvisionOrdersState - type: foreach - inputCollection: "${ .orders }" - iterationParam: singleorder - outputCollection: "${ .provisionedOrders }" - actions: - - functionRef: - refName: provisionOrderFunction - arguments: - order: "${ .singleorder }" - end: - produceEvents: - - eventRef: provisioningCompleteEvent - data: "${ .provisionedOrders }" \ No newline at end of file diff --git a/diagram/src/test/resources/examples/singleeventstate.json b/diagram/src/test/resources/examples/singleeventstate.json deleted file mode 100644 index ce027117..00000000 --- a/diagram/src/test/resources/examples/singleeventstate.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "id": "testEvents", - "name": "Test Events Workflow", - "description": "This is a test events workflow", - "version": "1.0", - "start": "EventState", - "events": [ - { - "name": "event1", - "source": "event1source", - "type": "event1type" - }, - { - "name": "event2", - "source": "evet2source", - "type": "event2type" - }, - { - "name": "event3", - "source": "event3source", - "type": "event3type" - }, - { - "name": "event4", - "source": "event4source", - "type": "event4type" - } - ], - "states": [ - { - "name": "EventState", - "type": "event", - "end": true, - "onEvents": [ - { - "eventRefs": ["event1", "event2"], - "actions": [] - }, - { - "eventRefs": ["event3", "event4"], - "actions": [] - } - ] - } - ] -} \ No newline at end of file diff --git a/diagram/src/test/resources/examples/singleeventstate.yml b/diagram/src/test/resources/examples/singleeventstate.yml deleted file mode 100644 index 874ddf9c..00000000 --- a/diagram/src/test/resources/examples/singleeventstate.yml +++ /dev/null @@ -1,32 +0,0 @@ ---- -id: testEvents -name: Test Events Workflow -description: This is a test events workflow -version: '1.0' -start: EventState -events: - - name: event1 - source: event1source - type: event1type - - name: event2 - source: evet2source - type: event2type - - name: event3 - source: event3source - type: event3type - - name: event4 - source: event4source - type: event4type -states: - - name: EventState - type: event - end: true - onEvents: - - eventRefs: - - event1 - - event2 - actions: [] - - eventRefs: - - event3 - - event4 - actions: [] diff --git a/diagram/src/test/resources/examples/singleswitchstate.json b/diagram/src/test/resources/examples/singleswitchstate.json deleted file mode 100644 index 5531b55d..00000000 --- a/diagram/src/test/resources/examples/singleswitchstate.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "id": "testSwitch", - "name": "Test Switch State Workflow", - "description": "This is a test switch state workflow", - "version": "1.0", - "start": "SwitchIt", - "states": [ - { - "name": "SwitchIt", - "type": "switch", - "dataConditions": [ - { - "name": "first", - "condition": "", - "transition": "FromFirstCondition" - }, - { - "name": "second", - "condition": "", - "transition": "FromSecondCondition" - }, - { - "name": "third", - "condition": "", - "end": true - }, - { - "name": "fourth", - "condition": "", - "end": true - } - ] - }, - { - "name": "FromFirstCondition", - "type": "delay", - "timeDelay": "PT2M", - "end": true - }, - { - "name": "FromSecondCondition", - "type": "inject", - "data": {}, - "end": true - } - ] -} \ No newline at end of file diff --git a/diagram/src/test/resources/examples/singleswitchstate.yml b/diagram/src/test/resources/examples/singleswitchstate.yml deleted file mode 100644 index bf9dc2f3..00000000 --- a/diagram/src/test/resources/examples/singleswitchstate.yml +++ /dev/null @@ -1,30 +0,0 @@ ---- -id: testSwitch -name: Test Switch State Workflow -description: This is a test switch state workflow -version: '1.0' -start: SwitchIt -states: - - name: SwitchIt - type: switch - dataConditions: - - name: first - condition: '' - transition: FromFirstCondition - - name: second - condition: '' - transition: FromSecondCondition - - name: third - condition: '' - end: true - - name: fourth - condition: '' - end: true - - name: FromFirstCondition - type: delay - timeDelay: PT2M - end: true - - name: FromSecondCondition - type: inject - data: {} - end: true diff --git a/diagram/src/test/resources/examples/singleswitchstateeventconditions.json b/diagram/src/test/resources/examples/singleswitchstateeventconditions.json deleted file mode 100644 index 9f0fd698..00000000 --- a/diagram/src/test/resources/examples/singleswitchstateeventconditions.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "id": "testSwitch", - "name": "Test Switch State Workflow", - "description": "This is a test switch state workflow", - "version": "1.0", - "start": "SwitchIt", - "states": [ - { - "name": "SwitchIt", - "type": "switch", - "eventConditions": [ - { - "name": "first", - "eventRef": "firstEvent", - "transition": "FromFirstCondition" - }, - { - "name": "second", - "eventRef": "secondEvent", - "transition": "FromSecondCondition" - }, - { - "name": "third", - "eventRef": "thirdEvent", - "end": true - }, - { - "name": "fourth", - "eventRef": "fourthEvent", - "end": true - } - ] - }, - { - "name": "FromFirstCondition", - "type": "delay", - "timeDelay": "PT2M", - "end": true - }, - { - "name": "FromSecondCondition", - "type": "inject", - "data": {}, - "end": true - } - ] -} \ No newline at end of file diff --git a/diagram/src/test/resources/examples/singleswitchstateeventconditions.yml b/diagram/src/test/resources/examples/singleswitchstateeventconditions.yml deleted file mode 100644 index e7934fdc..00000000 --- a/diagram/src/test/resources/examples/singleswitchstateeventconditions.yml +++ /dev/null @@ -1,30 +0,0 @@ ---- -id: testSwitch -name: Test Switch State Workflow -description: This is a test switch state workflow -version: '1.0' -start: SwitchIt -states: - - name: SwitchIt - type: switch - eventConditions: - - name: first - eventRef: firstEvent - transition: FromFirstCondition - - name: second - eventRef: secondEvent - transition: FromSecondCondition - - name: third - eventRef: thirdEvent - end: true - - name: fourth - eventRef: fourthEvent - end: true - - name: FromFirstCondition - type: delay - timeDelay: PT2M - end: true - - name: FromSecondCondition - type: inject - data: {} - end: true diff --git a/diagram/src/test/resources/examples/solvemathproblems.json b/diagram/src/test/resources/examples/solvemathproblems.json deleted file mode 100644 index 25e00e33..00000000 --- a/diagram/src/test/resources/examples/solvemathproblems.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "id": "solvemathproblems", - "version": "1.0", - "name": "Solve Math Problems Workflow", - "description": "Solve math problems", - "start": "Solve", - "functions": [ - { - "name": "solveMathExpressionFunction", - "operation": "http://myapis.org/mapthapis.json#solveExpression" - } - ], - "states":[ - { - "name":"Solve", - "type":"foreach", - "inputCollection": "${ .expressions }", - "iterationParam": "singleexpression", - "outputCollection": "${ .results }", - "actions":[ - { - "functionRef": { - "refName": "solveMathExpressionFunction", - "arguments": { - "expression": "${ .singleexpression }" - } - } - } - ], - "stateDataFilter": { - "output": "${ .results }" - }, - "end": true - } - ] -} \ No newline at end of file diff --git a/diagram/src/test/resources/examples/solvemathproblems.yml b/diagram/src/test/resources/examples/solvemathproblems.yml deleted file mode 100644 index b7bd1e27..00000000 --- a/diagram/src/test/resources/examples/solvemathproblems.yml +++ /dev/null @@ -1,22 +0,0 @@ -id: solvemathproblems -version: '1.0' -name: Solve Math Problems Workflow -description: Solve math problems -start: Solve -functions: - - name: solveMathExpressionFunction - operation: http://myapis.org/mapthapis.json#solveExpression -states: - - name: Solve - type: foreach - inputCollection: "${ .expressions }" - iterationParam: singleexpression - outputCollection: "${ .results }" - actions: - - functionRef: - refName: solveMathExpressionFunction - arguments: - expression: "${ .singleexpression }" - stateDataFilter: - output: "${ .results }" - end: true \ No newline at end of file diff --git a/diagram/src/test/resources/examples/vetappointmentservice.json b/diagram/src/test/resources/examples/vetappointmentservice.json deleted file mode 100644 index 230e0f73..00000000 --- a/diagram/src/test/resources/examples/vetappointmentservice.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "id": "VetAppointmentWorkflow", - "name": "Vet Appointment Workflow", - "description": "Vet service call via events", - "version": "1.0", - "start": "MakeVetAppointmentState", - "events": [ - { - "name": "MakeVetAppointment", - "source": "VetServiceSoure", - "kind": "produced" - }, - { - "name": "VetAppointmentInfo", - "source": "VetServiceSource", - "kind": "consumed" - } - ], - "states": [ - { - "name": "MakeVetAppointmentState", - "type": "operation", - "actions": [ - { - "name": "MakeAppointmentAction", - "eventRef": { - "triggerEventRef": "MakeVetAppointment", - "data": "${ .patientInfo }", - "resultEventRef": "VetAppointmentInfo" - }, - "actionDataFilter": { - "results": "${ .appointmentInfo }" - }, - "timeout": "PT15M" - } - ], - "end": true - } - ] -} \ No newline at end of file diff --git a/diagram/src/test/resources/examples/vetappointmentservice.yml b/diagram/src/test/resources/examples/vetappointmentservice.yml deleted file mode 100644 index d976067e..00000000 --- a/diagram/src/test/resources/examples/vetappointmentservice.yml +++ /dev/null @@ -1,25 +0,0 @@ -id: VetAppointmentWorkflow -name: Vet Appointment Workflow -description: Vet service call via events -version: '1.0' -start: MakeVetAppointmentState -events: - - name: MakeVetAppointment - source: VetServiceSoure - kind: produced - - name: VetAppointmentInfo - source: VetServiceSource - kind: consumed -states: - - name: MakeVetAppointmentState - type: operation - actions: - - name: MakeAppointmentAction - eventRef: - triggerEventRef: MakeVetAppointment - data: "${ .patientInfo }" - resultEventRef: VetAppointmentInfo - actionDataFilter: - results: "${ .appointmentInfo }" - timeout: PT15M - end: true \ No newline at end of file diff --git a/examples/events/pom.xml b/examples/events/pom.xml new file mode 100644 index 00000000..439b3a11 --- /dev/null +++ b/examples/events/pom.xml @@ -0,0 +1,21 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-examples + 8.0.0-SNAPSHOT + + Serverless Workflow :: Examples :: Events + serverlessworkflow-examples-events + + + io.serverlessworkflow + serverlessworkflow-impl-jackson + + + org.slf4j + slf4j-simple + + + \ No newline at end of file diff --git a/examples/events/src/main/java/events/EventExample.java b/examples/events/src/main/java/events/EventExample.java new file mode 100644 index 00000000..628782fb --- /dev/null +++ b/examples/events/src/main/java/events/EventExample.java @@ -0,0 +1,50 @@ +/* + * 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 events; + +import io.serverlessworkflow.api.WorkflowReader; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowDefinition; +import io.serverlessworkflow.impl.WorkflowInstance; +import java.io.IOException; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class EventExample { + + private static final Logger logger = LoggerFactory.getLogger(EventExample.class); + + public static void main(String[] args) throws IOException { + try (WorkflowApplication appl = WorkflowApplication.builder().build()) { + WorkflowDefinition listenDefinition = + appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath("listen.yaml")); + WorkflowDefinition emitDefinition = + appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath("emit.yaml")); + WorkflowInstance waitingInstance = listenDefinition.instance(Map.of()); + waitingInstance + .start() + .thenAccept(node -> logger.info("Waiting instance completed with result {}", node)); + logger.info("Listen instance waiting for proper event, Status {}", waitingInstance.status()); + logger.info("Publishing event with temperature 35"); + emitDefinition.instance(Map.of("temperature", 35)).start().join(); + logger.info( + "Listen instance still waiting for proper event, Status {}", waitingInstance.status()); + logger.info("Publishing event with temperature 39"); + emitDefinition.instance(Map.of("temperature", 39)).start().join(); + } + } +} diff --git a/examples/events/src/main/resources/emit.yaml b/examples/events/src/main/resources/emit.yaml new file mode 100644 index 00000000..4d14b030 --- /dev/null +++ b/examples/events/src/main/resources/emit.yaml @@ -0,0 +1,14 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: emit + version: '0.1.0' +do: + - emitEvent: + emit: + event: + with: + source: https://hospital.com + type: com.fake-hospital.vitals.measurements.temperature + data: + temperature: ${.temperature} \ No newline at end of file diff --git a/examples/events/src/main/resources/listen.yaml b/examples/events/src/main/resources/listen.yaml new file mode 100644 index 00000000..e49cea92 --- /dev/null +++ b/examples/events/src/main/resources/listen.yaml @@ -0,0 +1,13 @@ +document: + dsl: '1.0.0-alpha5' + namespace: examples + name: listen + version: '0.1.0' +do: + - callDoctor: + listen: + to: + one: + with: + type: com.fake-hospital.vitals.measurements.temperature + data: ${ .temperature > 38 } \ No newline at end of file diff --git a/examples/pom.xml b/examples/pom.xml new file mode 100644 index 00000000..3ab3e22a --- /dev/null +++ b/examples/pom.xml @@ -0,0 +1,56 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-parent + 8.0.0-SNAPSHOT + + Serverless Workflow :: Examples + serverlessworkflow-examples + + 3.1.10 + + pom + + + + io.serverlessworkflow + serverlessworkflow-impl-core + ${project.version} + + + io.serverlessworkflow + serverlessworkflow-impl-http + ${project.version} + + + io.serverlessworkflow + serverlessworkflow-impl-jackson + ${project.version} + + + org.slf4j + slf4j-simple + ${version.org.slf4j} + runtime + + + org.glassfish.jersey.core + jersey-client + ${version.org.glassfish.jersey} + runtime + + + org.glassfish.jersey.media + jersey-media-json-jackson + ${version.org.glassfish.jersey} + runtime + + + + + simpleGet + events + + \ No newline at end of file diff --git a/examples/simpleGet/pom.xml b/examples/simpleGet/pom.xml new file mode 100644 index 00000000..1a913363 --- /dev/null +++ b/examples/simpleGet/pom.xml @@ -0,0 +1,33 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-examples + 8.0.0-SNAPSHOT + + serverlessworkflow-examples-simpleGet + Serverless Workflow :: Examples :: SimpleGet + + + io.serverlessworkflow + serverlessworkflow-impl-jackson + + + io.serverlessworkflow + serverlessworkflow-impl-http + + + org.glassfish.jersey.media + jersey-media-json-jackson + + + org.glassfish.jersey.core + jersey-client + + + org.slf4j + slf4j-simple + + + \ No newline at end of file diff --git a/examples/simpleGet/src/main/java/io/serverlessworkflow/impl/BlockingExample.java b/examples/simpleGet/src/main/java/io/serverlessworkflow/impl/BlockingExample.java new file mode 100644 index 00000000..233d121f --- /dev/null +++ b/examples/simpleGet/src/main/java/io/serverlessworkflow/impl/BlockingExample.java @@ -0,0 +1,38 @@ +/* + * 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.impl; + +import io.serverlessworkflow.api.WorkflowReader; +import java.io.IOException; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BlockingExample { + + private static final Logger logger = LoggerFactory.getLogger(BlockingExample.class); + + public static void main(String[] args) throws IOException { + try (WorkflowApplication appl = WorkflowApplication.builder().build()) { + logger.info( + "Workflow output is {}", + appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath("get.yaml")) + .instance(Map.of("petId", 10)) + .start() + .join()); + } + } +} diff --git a/examples/simpleGet/src/main/java/io/serverlessworkflow/impl/NotBlockingExample.java b/examples/simpleGet/src/main/java/io/serverlessworkflow/impl/NotBlockingExample.java new file mode 100644 index 00000000..cb663c1a --- /dev/null +++ b/examples/simpleGet/src/main/java/io/serverlessworkflow/impl/NotBlockingExample.java @@ -0,0 +1,37 @@ +/* + * 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.impl; + +import io.serverlessworkflow.api.WorkflowReader; +import java.io.IOException; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NotBlockingExample { + + private static final Logger logger = LoggerFactory.getLogger(NotBlockingExample.class); + + public static void main(String[] args) throws IOException { + try (WorkflowApplication appl = WorkflowApplication.builder().build()) { + appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath("get.yaml")) + .instance(Map.of("petId", 10)) + .start() + .thenAccept(node -> logger.info("Workflow output is {}", node)); + logger.info("The request has been sent, this thread might continue doing stuff"); + } + } +} diff --git a/examples/simpleGet/src/main/resources/get.yaml b/examples/simpleGet/src/main/resources/get.yaml new file mode 100644 index 00000000..7adf3132 --- /dev/null +++ b/examples/simpleGet/src/main/resources/get.yaml @@ -0,0 +1,11 @@ +document: + dsl: '1.0.0-alpha5' + namespace: examples + name: call-http-shorthand-endpoint + version: '0.1.0' +do: + - getPet: + call: http + with: + method: get + endpoint: https://petstore.swagger.io/v2/pet/{petId} diff --git a/experimental/agentic/pom.xml b/experimental/agentic/pom.xml new file mode 100644 index 00000000..dec8a5e5 --- /dev/null +++ b/experimental/agentic/pom.xml @@ -0,0 +1,46 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-experimental + 8.0.0-SNAPSHOT + + serverlessworkflow-experimental-agentic + ServelessWorkflow:: Experimental:: Agentic + + + io.serverlessworkflow + serverlessworkflow-experimental-lambda + + + dev.langchain4j + langchain4j-agentic + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.assertj + assertj-core + test + + + ch.qos.logback + logback-classic + test + + + \ No newline at end of file diff --git a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticExpressionFactory.java b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticExpressionFactory.java new file mode 100644 index 00000000..a4b79a18 --- /dev/null +++ b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticExpressionFactory.java @@ -0,0 +1,33 @@ +/* + * 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.impl.expressions.agentic; + +import io.serverlessworkflow.impl.WorkflowModelFactory; +import io.serverlessworkflow.impl.expressions.func.JavaExpressionFactory; + +public class AgenticExpressionFactory extends JavaExpressionFactory { + + private final WorkflowModelFactory modelFactory = new AgenticModelFactory(); + + @Override + public WorkflowModelFactory modelFactory() { + return modelFactory; + } + + public int priority() { + return DEFAULT_PRIORITY - 1; + } +} diff --git a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModel.java b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModel.java new file mode 100644 index 00000000..9ab57838 --- /dev/null +++ b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModel.java @@ -0,0 +1,52 @@ +/* + * 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.impl.expressions.agentic; + +import dev.langchain4j.agentic.cognisphere.Cognisphere; +import dev.langchain4j.agentic.cognisphere.ResultWithCognisphere; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.expressions.func.JavaModel; +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; + +class AgenticModel extends JavaModel { + + private final Cognisphere cognisphere; + + AgenticModel(Object object, Cognisphere cognisphere) { + super(object); + this.cognisphere = cognisphere; + } + + @Override + public Collection asCollection() { + return object instanceof Collection value + ? new AgenticModelCollection(value, cognisphere) + : Collections.emptyList(); + } + + @Override + public Optional as(Class clazz) { + if (Cognisphere.class.isAssignableFrom(clazz)) { + return Optional.of(clazz.cast(cognisphere)); + } else if (ResultWithCognisphere.class.isAssignableFrom(clazz)) { + return Optional.of(clazz.cast(new ResultWithCognisphere<>(cognisphere, object))); + } else { + return super.as(clazz); + } + } +} diff --git a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelCollection.java b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelCollection.java new file mode 100644 index 00000000..e9440fb5 --- /dev/null +++ b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelCollection.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.impl.expressions.agentic; + +import dev.langchain4j.agentic.cognisphere.Cognisphere; +import dev.langchain4j.agentic.cognisphere.ResultWithCognisphere; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.expressions.func.JavaModelCollection; +import java.util.Collection; +import java.util.Optional; + +class AgenticModelCollection extends JavaModelCollection { + + private final Cognisphere cognisphere; + + AgenticModelCollection(Collection object, Cognisphere cognisphere) { + super(object); + this.cognisphere = cognisphere; + } + + AgenticModelCollection(Cognisphere cognisphere) { + this.cognisphere = cognisphere; + } + + @Override + protected WorkflowModel nextItem(Object obj) { + return new AgenticModel(obj, cognisphere); + } + + @Override + public Optional as(Class clazz) { + if (Cognisphere.class.isAssignableFrom(clazz)) { + return Optional.of(clazz.cast(cognisphere)); + } else if (ResultWithCognisphere.class.isAssignableFrom(clazz)) { + return Optional.of(clazz.cast(new ResultWithCognisphere<>(cognisphere, object))); + } else { + return super.as(clazz); + } + } +} diff --git a/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelFactory.java b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelFactory.java new file mode 100644 index 00000000..dc7c53ac --- /dev/null +++ b/experimental/agentic/src/main/java/io/serverlessworkflow/impl/expressions/agentic/AgenticModelFactory.java @@ -0,0 +1,90 @@ +/* + * 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.impl.expressions.agentic; + +import dev.langchain4j.agentic.cognisphere.Cognisphere; +import dev.langchain4j.agentic.cognisphere.CognisphereRegistry; +import io.cloudevents.CloudEvent; +import io.cloudevents.CloudEventData; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowModelCollection; +import io.serverlessworkflow.impl.WorkflowModelFactory; +import java.time.OffsetDateTime; +import java.util.Map; + +class AgenticModelFactory implements WorkflowModelFactory { + private final Cognisphere cognisphere = CognisphereRegistry.createEphemeralCognisphere(); + + private final AgenticModel TrueModel = new AgenticModel(Boolean.TRUE, cognisphere); + private final AgenticModel FalseModel = new AgenticModel(Boolean.FALSE, cognisphere); + private final AgenticModel NullModel = new AgenticModel(null, cognisphere); + + @Override + public WorkflowModel combine(Map workflowVariables) { + return new AgenticModel(workflowVariables, cognisphere); + } + + @Override + public WorkflowModelCollection createCollection() { + return new AgenticModelCollection(cognisphere); + } + + @Override + public WorkflowModel from(boolean value) { + return value ? TrueModel : FalseModel; + } + + @Override + public WorkflowModel from(Number value) { + return new AgenticModel(value, cognisphere); + } + + @Override + public WorkflowModel from(String value) { + return new AgenticModel(value, cognisphere); + } + + @Override + public WorkflowModel from(CloudEvent ce) { + return new AgenticModel(ce, cognisphere); + } + + @Override + public WorkflowModel from(CloudEventData ce) { + return new AgenticModel(ce, cognisphere); + } + + @Override + public WorkflowModel from(OffsetDateTime value) { + return new AgenticModel(value, cognisphere); + } + + @Override + public WorkflowModel from(Map map) { + cognisphere.writeStates(map); + return new AgenticModel(map, cognisphere); + } + + @Override + public WorkflowModel fromNull() { + return NullModel; + } + + @Override + public WorkflowModel fromOther(Object value) { + return new AgenticModel(value, cognisphere); + } +} diff --git a/experimental/agentic/src/main/resources/META-INF/services/io.serverlessworkflow.impl.expressions.ExpressionFactory b/experimental/agentic/src/main/resources/META-INF/services/io.serverlessworkflow.impl.expressions.ExpressionFactory new file mode 100644 index 00000000..e0038ed9 --- /dev/null +++ b/experimental/agentic/src/main/resources/META-INF/services/io.serverlessworkflow.impl.expressions.ExpressionFactory @@ -0,0 +1 @@ +io.serverlessworkflow.impl.expressions.agentic.AgenticExpressionFactory \ No newline at end of file diff --git a/experimental/lambda-fluent/pom.xml b/experimental/lambda-fluent/pom.xml new file mode 100644 index 00000000..a537b36c --- /dev/null +++ b/experimental/lambda-fluent/pom.xml @@ -0,0 +1,32 @@ + + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-experimental + 8.0.0-SNAPSHOT + + + serverlessworkflow-lambda-fluent + Serverless Workflow :: Experimental :: Lambda Fluent + pom + + + 17 + 17 + UTF-8 + + + + + io.serverlessworkflow + serverlessworkflow-fluent-func + + + io.serverlessworkflow + serverlessworkflow-experimental-lambda + + + \ No newline at end of file diff --git a/spi/pom.xml b/experimental/lambda/pom.xml similarity index 61% rename from spi/pom.xml rename to experimental/lambda/pom.xml index cd717682..0b57ab0a 100644 --- a/spi/pom.xml +++ b/experimental/lambda/pom.xml @@ -1,33 +1,27 @@ - + 4.0.0 - io.serverlessworkflow - serverlessworkflow-parent - 3.0.0-SNAPSHOT + serverlessworkflow-experimental + 8.0.0-SNAPSHOT - - serverlessworkflow-spi - Serverless Workflow :: SPI - ${project.parent.version} - jar - Java SDK for Serverless Workflow Specification - + serverlessworkflow-experimental-lambda + Serverless Workflow :: Experimental :: Lambda - org.slf4j - slf4j-api + io.serverlessworkflow + serverlessworkflow-experimental-types + + + io.serverlessworkflow + serverlessworkflow-impl-core - io.serverlessworkflow - serverlessworkflow-api - ${project.version} + serverlessworkflow-fluent-func + test - - org.junit.jupiter junit-jupiter-api @@ -44,8 +38,8 @@ test - org.mockito - mockito-core + org.assertj + assertj-core test @@ -53,10 +47,5 @@ logback-classic test - - org.assertj - assertj-core - test - \ No newline at end of file diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaConsumerCallExecutor.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaConsumerCallExecutor.java new file mode 100644 index 00000000..4c1abce7 --- /dev/null +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaConsumerCallExecutor.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.impl.executors.func; + +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.api.types.func.CallJava; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.executors.CallableTask; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +public class JavaConsumerCallExecutor implements CallableTask { + + private Consumer consumer; + + public void init( + CallJava.CallJavaConsumer task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader loader) { + consumer = task.consumer(); + } + + @Override + public CompletableFuture apply( + WorkflowContext workflowContext, TaskContext taskContext, WorkflowModel input) { + consumer.accept(input.asJavaObject()); + return CompletableFuture.completedFuture(input); + } + + @Override + public boolean accept(Class clazz) { + return CallJava.CallJavaConsumer.class.isAssignableFrom(clazz); + } +} diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaForExecutorBuilder.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaForExecutorBuilder.java new file mode 100644 index 00000000..e427a222 --- /dev/null +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaForExecutorBuilder.java @@ -0,0 +1,81 @@ +/* + * 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.impl.executors.func; + +import static io.serverlessworkflow.impl.executors.func.JavaFuncUtils.safeObject; + +import io.serverlessworkflow.api.types.ForTask; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.api.types.func.ForTaskFunction; +import io.serverlessworkflow.api.types.func.TypedFunction; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.WorkflowUtils; +import io.serverlessworkflow.impl.executors.ForExecutor.ForExecutorBuilder; +import io.serverlessworkflow.impl.expressions.LoopPredicateIndex; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.util.Optional; + +public class JavaForExecutorBuilder extends ForExecutorBuilder { + + protected JavaForExecutorBuilder( + WorkflowPosition position, + ForTask task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader resourceLoader) { + super(position, task, workflow, application, resourceLoader); + } + + protected Optional buildWhileFilter() { + if (task instanceof ForTaskFunction taskFunctions) { + final LoopPredicateIndex whilePred = taskFunctions.getWhilePredicate(); + Optional> whileClass = taskFunctions.getWhileClass(); + String varName = task.getFor().getEach(); + String indexName = task.getFor().getAt(); + if (whilePred != null) { + return Optional.of( + (w, t, n) -> { + Object item = safeObject(t.variables().get(varName)); + return application + .modelFactory() + .from( + whilePred.test( + JavaFuncUtils.convert(n, whileClass), + item, + (Integer) safeObject(t.variables().get(indexName)))); + }); + } + } + return super.buildWhileFilter(); + } + + protected WorkflowFilter buildCollectionFilter() { + return task instanceof ForTaskFunction taskFunctions + ? WorkflowUtils.buildWorkflowFilter( + application, null, collectionFilterObject(taskFunctions)) + : super.buildCollectionFilter(); + } + + private Object collectionFilterObject(ForTaskFunction taskFunctions) { + return taskFunctions.getForClass().isPresent() + ? new TypedFunction( + taskFunctions.getCollection(), taskFunctions.getForClass().orElseThrow()) + : taskFunctions.getCollection(); + } +} diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaFuncUtils.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaFuncUtils.java new file mode 100644 index 00000000..ed42bf50 --- /dev/null +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaFuncUtils.java @@ -0,0 +1,52 @@ +/* + * 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.impl.executors.func; + +import io.serverlessworkflow.impl.WorkflowModel; +import java.util.Optional; + +public class JavaFuncUtils { + + static Object safeObject(Object obj) { + return obj instanceof WorkflowModel model ? model.asJavaObject() : obj; + } + + static T convertT(WorkflowModel model, Optional> inputClass) { + return inputClass + .map( + c -> + model + .as(c) + .orElseThrow( + () -> + new IllegalArgumentException( + "Model " + model + " cannot be converted to type " + c))) + .orElseGet(() -> (T) model.asJavaObject()); + } + + static Object convert(WorkflowModel model, Optional> inputClass) { + return inputClass.isPresent() + ? model + .as(inputClass.orElseThrow()) + .orElseThrow( + () -> + new IllegalArgumentException( + "Model " + model + " cannot be converted to type " + inputClass)) + : model.asJavaObject(); + } + + private JavaFuncUtils() {} +} diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaFunctionCallExecutor.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaFunctionCallExecutor.java new file mode 100644 index 00000000..9b040772 --- /dev/null +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaFunctionCallExecutor.java @@ -0,0 +1,63 @@ +/* + * 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.impl.executors.func; + +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.api.types.func.CallJava; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowModelFactory; +import io.serverlessworkflow.impl.executors.CallableTask; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +public class JavaFunctionCallExecutor + implements CallableTask> { + + private Function function; + private Optional> inputClass; + + static String fromInt(Integer integer) { + return Integer.toString(integer); + } + + public void init( + CallJava.CallJavaFunction task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader loader) { + function = task.function(); + inputClass = task.inputClass(); + } + + @Override + public CompletableFuture apply( + WorkflowContext workflowContext, TaskContext taskContext, WorkflowModel input) { + WorkflowModelFactory modelFactory = workflowContext.definition().application().modelFactory(); + return CompletableFuture.completedFuture( + modelFactory.fromAny(function.apply(JavaFuncUtils.convertT(input, inputClass)))); + } + + @Override + public boolean accept(Class clazz) { + return CallJava.CallJavaFunction.class.isAssignableFrom(clazz); + } +} diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaLoopFunctionCallExecutor.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaLoopFunctionCallExecutor.java new file mode 100644 index 00000000..31c2100f --- /dev/null +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaLoopFunctionCallExecutor.java @@ -0,0 +1,62 @@ +/* + * 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.impl.executors.func; + +import static io.serverlessworkflow.impl.executors.func.JavaFuncUtils.safeObject; + +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.api.types.func.CallJava; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowModelFactory; +import io.serverlessworkflow.impl.executors.CallableTask; +import io.serverlessworkflow.impl.expressions.LoopFunction; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.util.concurrent.CompletableFuture; + +public class JavaLoopFunctionCallExecutor implements CallableTask { + + private LoopFunction function; + private String varName; + + public void init( + CallJava.CallJavaLoopFunction task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader loader) { + function = task.function(); + varName = task.varName(); + } + + @Override + public CompletableFuture apply( + WorkflowContext workflowContext, TaskContext taskContext, WorkflowModel input) { + WorkflowModelFactory modelFactory = workflowContext.definition().application().modelFactory(); + return CompletableFuture.completedFuture( + modelFactory.fromAny( + function.apply( + input.asJavaObject(), safeObject(taskContext.variables().get(varName))))); + } + + @Override + public boolean accept(Class clazz) { + + return CallJava.CallJavaLoopFunction.class.isAssignableFrom(clazz); + } +} diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaLoopFunctionIndexCallExecutor.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaLoopFunctionIndexCallExecutor.java new file mode 100644 index 00000000..e566c0a0 --- /dev/null +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaLoopFunctionIndexCallExecutor.java @@ -0,0 +1,67 @@ +/* + * 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.impl.executors.func; + +import static io.serverlessworkflow.impl.executors.func.JavaFuncUtils.safeObject; + +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.api.types.func.CallJava; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowModelFactory; +import io.serverlessworkflow.impl.executors.CallableTask; +import io.serverlessworkflow.impl.expressions.LoopFunctionIndex; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.util.concurrent.CompletableFuture; + +public class JavaLoopFunctionIndexCallExecutor + implements CallableTask { + + private LoopFunctionIndex function; + private String varName; + private String indexName; + + public void init( + CallJava.CallJavaLoopFunctionIndex task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader loader) { + function = task.function(); + varName = task.varName(); + indexName = task.indexName(); + } + + @Override + public CompletableFuture apply( + WorkflowContext workflowContext, TaskContext taskContext, WorkflowModel input) { + WorkflowModelFactory modelFactory = workflowContext.definition().application().modelFactory(); + + return CompletableFuture.completedFuture( + modelFactory.fromAny( + function.apply( + input.asJavaObject(), + safeObject(taskContext.variables().get(varName)), + (Integer) safeObject(taskContext.variables().get(indexName))))); + } + + @Override + public boolean accept(Class clazz) { + return CallJava.CallJavaLoopFunctionIndex.class.isAssignableFrom(clazz); + } +} diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaSwitchExecutorBuilder.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaSwitchExecutorBuilder.java new file mode 100644 index 00000000..7762638d --- /dev/null +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaSwitchExecutorBuilder.java @@ -0,0 +1,57 @@ +/* + * 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.impl.executors.func; + +import io.serverlessworkflow.api.types.SwitchCase; +import io.serverlessworkflow.api.types.SwitchTask; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.api.types.func.SwitchCaseFunction; +import io.serverlessworkflow.api.types.func.TypedPredicate; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.WorkflowUtils; +import io.serverlessworkflow.impl.executors.SwitchExecutor.SwitchExecutorBuilder; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.util.Optional; +import java.util.function.Predicate; + +public class JavaSwitchExecutorBuilder extends SwitchExecutorBuilder { + + protected JavaSwitchExecutorBuilder( + WorkflowPosition position, + SwitchTask task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader resourceLoader) { + super(position, task, workflow, application, resourceLoader); + } + + @Override + protected Optional buildFilter(SwitchCase switchCase) { + return switchCase instanceof SwitchCaseFunction function + ? Optional.of( + WorkflowUtils.buildWorkflowFilter( + application, null, predObject(function.predicate(), function.predicateClass()))) + : super.buildFilter(switchCase); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private Object predObject(Predicate pred, Optional> predClass) { + return predClass.isPresent() ? new TypedPredicate(pred, predClass.orElseThrow()) : pred; + } +} diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaTaskExecutorFactory.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaTaskExecutorFactory.java new file mode 100644 index 00000000..8dfce9de --- /dev/null +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/func/JavaTaskExecutorFactory.java @@ -0,0 +1,45 @@ +/* + * 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.impl.executors.func; + +import io.serverlessworkflow.api.types.Task; +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.executors.DefaultTaskExecutorFactory; +import io.serverlessworkflow.impl.executors.TaskExecutorBuilder; +import io.serverlessworkflow.impl.resources.ResourceLoader; + +public class JavaTaskExecutorFactory extends DefaultTaskExecutorFactory { + + public TaskExecutorBuilder getTaskExecutor( + WorkflowPosition position, + Task task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader resourceLoader) { + if (task.getForTask() != null) { + return new JavaForExecutorBuilder( + position, task.getForTask(), workflow, application, resourceLoader); + } else if (task.getSwitchTask() != null) { + return new JavaSwitchExecutorBuilder( + position, task.getSwitchTask(), workflow, application, resourceLoader); + } else { + return super.getTaskExecutor(position, task, workflow, application, resourceLoader); + } + } +} diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaExpressionFactory.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaExpressionFactory.java new file mode 100644 index 00000000..79bb6a8e --- /dev/null +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaExpressionFactory.java @@ -0,0 +1,103 @@ +/* + * 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.impl.expressions.func; + +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.api.types.TaskMetadata; +import io.serverlessworkflow.api.types.func.TypedFunction; +import io.serverlessworkflow.api.types.func.TypedPredicate; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowModelFactory; +import io.serverlessworkflow.impl.expressions.Expression; +import io.serverlessworkflow.impl.expressions.ExpressionFactory; +import io.serverlessworkflow.impl.expressions.TaskMetadataKeys; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.function.Predicate; + +public class JavaExpressionFactory implements ExpressionFactory { + + private final WorkflowModelFactory modelFactory = new JavaModelFactory(); + private final Expression dummyExpression = + new Expression() { + @Override + public WorkflowModel eval( + WorkflowContext workflowContext, TaskContext context, WorkflowModel model) { + return model; + } + }; + + @Override + public Expression buildExpression(String expression) { + return dummyExpression; + } + + @Override + public WorkflowFilter buildFilter(String expr, Object value) { + if (value instanceof Function func) { + return (w, t, n) -> modelFactory.fromAny(func.apply(n.asJavaObject())); + } else if (value instanceof TypedFunction func) { + return (w, t, n) -> + modelFactory.fromAny(func.function().apply(n.as(func.argClass()).orElseThrow())); + } else if (value instanceof Predicate pred) { + return fromPredicate(pred); + } else if (value instanceof TypedPredicate pred) { + return fromPredicate(pred); + } else if (value instanceof BiPredicate pred) { + return (w, t, n) -> modelFactory.from(pred.test(w, t)); + } else if (value instanceof BiFunction func) { + return (w, t, n) -> modelFactory.fromAny(func.apply(w, t)); + } else if (value instanceof WorkflowFilter filter) { + return filter; + } else { + return (w, t, n) -> modelFactory.fromAny(value); + } + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private WorkflowFilter fromPredicate(Predicate pred) { + return (w, t, n) -> modelFactory.from(pred.test(n.asJavaObject())); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private WorkflowFilter fromPredicate(TypedPredicate pred) { + return (w, t, n) -> modelFactory.from(pred.pred().test(n.as(pred.argClass()).orElseThrow())); + } + + @Override + public Optional buildIfFilter(TaskBase task) { + TaskMetadata metadata = task.getMetadata(); + if (metadata != null) { + Object obj = metadata.getAdditionalProperties().get(TaskMetadataKeys.IF_PREDICATE); + if (obj instanceof Predicate pred) { + return Optional.of(fromPredicate(pred)); + } else if (obj instanceof TypedPredicate pred) { + return Optional.of(fromPredicate(pred)); + } + } + return ExpressionFactory.super.buildIfFilter(task); + } + + @Override + public WorkflowModelFactory modelFactory() { + return modelFactory; + } +} diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaModel.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaModel.java new file mode 100644 index 00000000..11cff762 --- /dev/null +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaModel.java @@ -0,0 +1,120 @@ +/* + * 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.impl.expressions.func; + +import io.cloudevents.CloudEventData; +import io.serverlessworkflow.impl.WorkflowModel; +import java.time.OffsetDateTime; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +public class JavaModel implements WorkflowModel { + + protected final Object object; + + protected JavaModel(Object object) { + this.object = asJavaObject(object); + } + + @Override + public void forEach(BiConsumer consumer) { + asMap() + .ifPresent( + m -> + m.forEach( + (k, v) -> + consumer.accept( + k, v instanceof WorkflowModel model ? model : new JavaModel(v)))); + } + + @Override + public Optional asBoolean() { + return object instanceof Boolean value ? Optional.of(value) : Optional.empty(); + } + + @Override + public Collection asCollection() { + return object instanceof Collection value + ? new JavaModelCollection(value) + : Collections.emptyList(); + } + + @Override + public Optional asText() { + return object instanceof String value ? Optional.of(value) : Optional.empty(); + } + + @Override + public Optional asDate() { + return object instanceof OffsetDateTime value ? Optional.of(value) : Optional.empty(); + } + + @Override + public Optional asNumber() { + return object instanceof Number value ? Optional.of(value) : Optional.empty(); + } + + @Override + public Optional asCloudEventData() { + return object instanceof CloudEventData value ? Optional.of(value) : Optional.empty(); + } + + @Override + public Optional> asMap() { + return object instanceof Map ? Optional.of((Map) object) : Optional.empty(); + } + + @Override + public Object asJavaObject() { + return object; + } + + static Object asJavaObject(Object object) { + if (object instanceof WorkflowModel model) { + return model.asJavaObject(); + } else if (object instanceof Map map) { + return ((Map) map) + .entrySet().stream() + .collect(Collectors.toMap(Entry::getKey, e -> asJavaObject(e.getValue()))); + } else if (object instanceof Collection col) { + return col.stream().map(JavaModel::asJavaObject).collect(Collectors.toList()); + } else { + return object; + } + } + + @Override + public Object asIs() { + return object; + } + + @Override + public Class objectClass() { + return object != null ? object.getClass() : Object.class; + } + + @Override + public Optional as(Class clazz) { + return object != null && clazz.isAssignableFrom(object.getClass()) + ? Optional.of(clazz.cast(object)) + : Optional.empty(); + } +} diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaModelCollection.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaModelCollection.java new file mode 100644 index 00000000..12e0ab66 --- /dev/null +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaModelCollection.java @@ -0,0 +1,151 @@ +/* + * 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.impl.expressions.func; + +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowModelCollection; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Optional; + +public class JavaModelCollection implements Collection, WorkflowModelCollection { + + protected final Collection object; + + protected JavaModelCollection() { + this.object = new ArrayList<>(); + } + + protected JavaModelCollection(Collection object) { + this.object = (Collection) JavaModel.asJavaObject(object); + } + + @Override + public int size() { + return object.size(); + } + + @Override + public boolean isEmpty() { + return object.isEmpty(); + } + + @Override + public boolean contains(Object o) { + throw new UnsupportedOperationException(); + } + + private class ModelIterator implements Iterator { + + private Iterator wrapped; + + public ModelIterator(Iterator wrapped) { + this.wrapped = wrapped; + } + + @Override + public boolean hasNext() { + return wrapped.hasNext(); + } + + @Override + public WorkflowModel next() { + Object obj = wrapped.next(); + return obj instanceof WorkflowModel value ? value : nextItem(obj); + } + } + + protected WorkflowModel nextItem(Object obj) { + return new JavaModel(obj); + } + + @Override + public Iterator iterator() { + return new ModelIterator(object.iterator()); + } + + @Override + public Object[] toArray() { + throw new UnsupportedOperationException(); + } + + @Override + public T[] toArray(T[] a) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean add(WorkflowModel e) { + return object.add(e.asJavaObject()); + } + + @Override + public boolean remove(Object o) { + return object.remove(((WorkflowModel) o).asJavaObject()); + } + + @Override + public boolean containsAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection c) { + int size = size(); + c.forEach(this::add); + return size() > size; + } + + @Override + public boolean removeAll(Collection c) { + int size = size(); + c.forEach(this::remove); + return size() < size; + } + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + object.clear(); + } + + @Override + public Object asJavaObject() { + return object; + } + + @Override + public Object asIs() { + return object; + } + + @Override + public Class objectClass() { + return object.getClass(); + } + + @Override + public Optional as(Class clazz) { + return object.getClass().isAssignableFrom(clazz) + ? Optional.of(clazz.cast(object)) + : Optional.empty(); + } +} diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaModelFactory.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaModelFactory.java new file mode 100644 index 00000000..2ccc0dcd --- /dev/null +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/func/JavaModelFactory.java @@ -0,0 +1,85 @@ +/* + * 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.impl.expressions.func; + +import io.cloudevents.CloudEvent; +import io.cloudevents.CloudEventData; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowModelCollection; +import io.serverlessworkflow.impl.WorkflowModelFactory; +import java.time.OffsetDateTime; +import java.util.Map; + +class JavaModelFactory implements WorkflowModelFactory { + private final JavaModel TrueModel = new JavaModel(Boolean.TRUE); + private final JavaModel FalseModel = new JavaModel(Boolean.FALSE); + private final JavaModel NullModel = new JavaModel(null); + + @Override + public WorkflowModel combine(Map workflowVariables) { + return new JavaModel(workflowVariables); + } + + @Override + public WorkflowModelCollection createCollection() { + return new JavaModelCollection(); + } + + @Override + public WorkflowModel from(boolean value) { + return value ? TrueModel : FalseModel; + } + + @Override + public WorkflowModel from(Number value) { + return new JavaModel(value); + } + + @Override + public WorkflowModel from(String value) { + return new JavaModel(value); + } + + @Override + public WorkflowModel from(CloudEvent ce) { + return new JavaModel(ce); + } + + @Override + public WorkflowModel from(CloudEventData ce) { + return new JavaModel(ce); + } + + @Override + public WorkflowModel from(OffsetDateTime value) { + return new JavaModel(value); + } + + @Override + public WorkflowModel from(Map map) { + return new JavaModel(map); + } + + @Override + public WorkflowModel fromNull() { + return NullModel; + } + + @Override + public WorkflowModel fromAny(Object obj) { + return new JavaModel(obj); + } +} diff --git a/experimental/lambda/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.CallableTask b/experimental/lambda/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.CallableTask new file mode 100644 index 00000000..1b69b5d3 --- /dev/null +++ b/experimental/lambda/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.CallableTask @@ -0,0 +1,4 @@ +io.serverlessworkflow.impl.executors.func.JavaLoopFunctionIndexCallExecutor +io.serverlessworkflow.impl.executors.func.JavaLoopFunctionCallExecutor +io.serverlessworkflow.impl.executors.func.JavaFunctionCallExecutor +io.serverlessworkflow.impl.executors.func.JavaConsumerCallExecutor \ No newline at end of file diff --git a/experimental/lambda/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.TaskExecutorFactory b/experimental/lambda/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.TaskExecutorFactory new file mode 100644 index 00000000..710fa4db --- /dev/null +++ b/experimental/lambda/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.TaskExecutorFactory @@ -0,0 +1 @@ +io.serverlessworkflow.impl.executors.func.JavaTaskExecutorFactory \ No newline at end of file diff --git a/experimental/lambda/src/main/resources/META-INF/services/io.serverlessworkflow.impl.expressions.ExpressionFactory b/experimental/lambda/src/main/resources/META-INF/services/io.serverlessworkflow.impl.expressions.ExpressionFactory new file mode 100644 index 00000000..722ea0f0 --- /dev/null +++ b/experimental/lambda/src/main/resources/META-INF/services/io.serverlessworkflow.impl.expressions.ExpressionFactory @@ -0,0 +1 @@ +io.serverlessworkflow.impl.expressions.func.JavaExpressionFactory \ No newline at end of file diff --git a/experimental/lambda/src/test/java/io/serverless/workflow/impl/CallTest.java b/experimental/lambda/src/test/java/io/serverless/workflow/impl/CallTest.java new file mode 100644 index 00000000..078cc5e3 --- /dev/null +++ b/experimental/lambda/src/test/java/io/serverless/workflow/impl/CallTest.java @@ -0,0 +1,190 @@ +/* + * 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.serverless.workflow.impl; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.serverlessworkflow.api.types.Document; +import io.serverlessworkflow.api.types.FlowDirective; +import io.serverlessworkflow.api.types.FlowDirectiveEnum; +import io.serverlessworkflow.api.types.ForTaskConfiguration; +import io.serverlessworkflow.api.types.SwitchItem; +import io.serverlessworkflow.api.types.SwitchTask; +import io.serverlessworkflow.api.types.Task; +import io.serverlessworkflow.api.types.TaskItem; +import io.serverlessworkflow.api.types.TaskMetadata; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.api.types.func.CallJava; +import io.serverlessworkflow.api.types.func.CallTaskJava; +import io.serverlessworkflow.api.types.func.ForTaskFunction; +import io.serverlessworkflow.api.types.func.SwitchCaseFunction; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowDefinition; +import io.serverlessworkflow.impl.expressions.TaskMetadataKeys; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.function.Predicate; +import org.junit.jupiter.api.Test; + +class CallTest { + + @Test + void testJavaFunction() throws InterruptedException, ExecutionException { + try (WorkflowApplication app = WorkflowApplication.builder().build()) { + Workflow workflow = + new Workflow() + .withDocument( + new Document().withNamespace("test").withName("testJavaCall").withVersion("1.0")) + .withDo( + List.of( + new TaskItem( + "javaCall", + new Task() + .withCallTask( + new CallTaskJava( + CallJava.function(JavaFunctions::getName, Person.class)))))); + + assertThat( + app.workflowDefinition(workflow) + .instance(new Person("Francisco", 33)) + .start() + .get() + .asText() + .orElseThrow()) + .isEqualTo("Francisco Javierito"); + } + } + + @Test + void testForLoop() throws InterruptedException, ExecutionException { + try (WorkflowApplication app = WorkflowApplication.builder().build()) { + ForTaskConfiguration forConfig = new ForTaskConfiguration(); + Workflow workflow = + new Workflow() + .withDocument( + new Document().withNamespace("test").withName("testLoop").withVersion("1.0")) + .withDo( + List.of( + new TaskItem( + "forLoop", + new Task() + .withForTask( + new ForTaskFunction() + .withWhile(CallTest::isEven) + .withCollection(v -> v, Collection.class) + .withFor(forConfig) + .withDo( + List.of( + new TaskItem( + "javaCall", + new Task() + .withCallTask( + new CallTaskJava( + CallJava.loopFunction( + CallTest::sum, + forConfig.getEach())))))))))); + + assertThat( + app.workflowDefinition(workflow) + .instance(List.of(2, 4, 6, 7)) + .start() + .get() + .asNumber() + .orElseThrow()) + .isEqualTo(12); + } + } + + @Test + void testSwitch() throws InterruptedException, ExecutionException { + try (WorkflowApplication app = WorkflowApplication.builder().build()) { + Workflow workflow = + new Workflow() + .withDocument( + new Document().withNamespace("test").withName("testSwith").withVersion("1.0")) + .withDo( + List.of( + new TaskItem( + "switch", + new Task() + .withSwitchTask( + new SwitchTask() + .withSwitch( + List.of( + new SwitchItem( + "odd", + new SwitchCaseFunction() + .withPredicate(CallTest::isOdd, Integer.class) + .withThen( + new FlowDirective() + .withFlowDirectiveEnum( + FlowDirectiveEnum.END))))))), + new TaskItem( + "java", + new Task() + .withCallTask(new CallTaskJava(CallJava.function(CallTest::zero)))))); + + WorkflowDefinition definition = app.workflowDefinition(workflow); + assertThat(definition.instance(3).start().get().asNumber().orElseThrow()).isEqualTo(3); + assertThat(definition.instance(4).start().get().asNumber().orElseThrow()).isEqualTo(0); + } + } + + @Test + void testIf() throws InterruptedException, ExecutionException { + try (WorkflowApplication app = WorkflowApplication.builder().build()) { + Workflow workflow = + new Workflow() + .withDocument( + new Document().withNamespace("test").withName("testIf").withVersion("1.0")) + .withDo( + List.of( + new TaskItem( + "java", + new Task() + .withCallTask( + new CallTaskJava( + withPredicate( + CallJava.function(CallTest::zero), CallTest::isOdd)))))); + WorkflowDefinition definition = app.workflowDefinition(workflow); + assertThat(definition.instance(3).start().get().asNumber().orElseThrow()).isEqualTo(0); + assertThat(definition.instance(4).start().get().asNumber().orElseThrow()).isEqualTo(4); + } + } + + private CallJava withPredicate(CallJava call, Predicate pred) { + return (CallJava) + call.withMetadata( + new TaskMetadata().withAdditionalProperty(TaskMetadataKeys.IF_PREDICATE, pred)); + } + + public static boolean isEven(Object model, Integer number) { + return !isOdd(number); + } + + public static boolean isOdd(Integer number) { + return number % 2 != 0; + } + + public static int zero(Integer value) { + return 0; + } + + public static Integer sum(Object model, Integer item) { + return model instanceof Collection ? item : (Integer) model + item; + } +} diff --git a/experimental/lambda/src/test/java/io/serverless/workflow/impl/FluentDSLCallTest.java b/experimental/lambda/src/test/java/io/serverless/workflow/impl/FluentDSLCallTest.java new file mode 100644 index 00000000..cc34108d --- /dev/null +++ b/experimental/lambda/src/test/java/io/serverless/workflow/impl/FluentDSLCallTest.java @@ -0,0 +1,96 @@ +/* + * 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.serverless.workflow.impl; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.serverlessworkflow.api.types.FlowDirectiveEnum; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.fluent.func.FuncWorkflowBuilder; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowDefinition; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ExecutionException; +import org.junit.jupiter.api.Test; + +public class FluentDSLCallTest { + + @Test + void testJavaFunction() throws InterruptedException, ExecutionException { + try (WorkflowApplication app = WorkflowApplication.builder().build()) { + final Workflow workflow = + FuncWorkflowBuilder.workflow("testJavaCall") + .tasks(tasks -> tasks.callFn(f -> f.function(JavaFunctions::getName))) + .build(); + assertThat( + app.workflowDefinition(workflow) + .instance(new Person("Francisco", 33)) + .start() + .get() + .asText() + .orElseThrow()) + .isEqualTo("Francisco Javierito"); + } + } + + @Test + void testForLoop() throws InterruptedException, ExecutionException { + try (WorkflowApplication app = WorkflowApplication.builder().build()) { + Workflow workflow = + FuncWorkflowBuilder.workflow() + .tasks( + t -> + t.forEach( + f -> + f.whileC(CallTest::isEven) + .collection(v -> (Collection) v) + .tasks(CallTest::sum))) + .build(); + + assertThat( + app.workflowDefinition(workflow) + .instance(List.of(2, 4, 6)) + .start() + .get() + .asNumber() + .orElseThrow()) + .isEqualTo(12); + } + } + + @Test + void testSwitch() throws InterruptedException, ExecutionException { + try (WorkflowApplication app = WorkflowApplication.builder().build()) { + Workflow workflow = + FuncWorkflowBuilder.workflow() + .tasks( + tasks -> + tasks + .switchCase( + switchOdd -> + switchOdd.functions( + item -> + item.when(CallTest::isOdd).then(FlowDirectiveEnum.END))) + .callFn(callJava -> callJava.function(CallTest::zero))) + .build(); + + WorkflowDefinition definition = app.workflowDefinition(workflow); + assertThat(definition.instance(3).start().get().asNumber().orElseThrow()).isEqualTo(3); + assertThat(definition.instance(4).start().get().asNumber().orElseThrow()).isEqualTo(0); + } + } +} diff --git a/experimental/lambda/src/test/java/io/serverless/workflow/impl/JavaFunctions.java b/experimental/lambda/src/test/java/io/serverless/workflow/impl/JavaFunctions.java new file mode 100644 index 00000000..f24766aa --- /dev/null +++ b/experimental/lambda/src/test/java/io/serverless/workflow/impl/JavaFunctions.java @@ -0,0 +1,37 @@ +/* + * 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.serverless.workflow.impl; + +import java.util.Map; + +public class JavaFunctions { + + static Person personPojo(String name) { + return new Person(name + " Javierito", 23); + } + + static String getName(Person person) { + return person.name() + " Javierito"; + } + + static Map addJavierito(Map map) { + return Map.of("name", map.get("name") + " Javierito"); + } + + static String addJavieritoString(String value) { + return value + " Javierito"; + } +} diff --git a/experimental/lambda/src/test/java/io/serverless/workflow/impl/ModelTest.java b/experimental/lambda/src/test/java/io/serverless/workflow/impl/ModelTest.java new file mode 100644 index 00000000..8c917dbe --- /dev/null +++ b/experimental/lambda/src/test/java/io/serverless/workflow/impl/ModelTest.java @@ -0,0 +1,176 @@ +/* + * 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.serverless.workflow.impl; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.serverlessworkflow.api.types.Document; +import io.serverlessworkflow.api.types.DurationInline; +import io.serverlessworkflow.api.types.Output; +import io.serverlessworkflow.api.types.Set; +import io.serverlessworkflow.api.types.SetTask; +import io.serverlessworkflow.api.types.SetTaskConfiguration; +import io.serverlessworkflow.api.types.Task; +import io.serverlessworkflow.api.types.TaskItem; +import io.serverlessworkflow.api.types.TimeoutAfter; +import io.serverlessworkflow.api.types.WaitTask; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.api.types.func.OutputAsFunction; +import io.serverlessworkflow.impl.WorkflowApplication; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import org.junit.jupiter.api.Test; + +class ModelTest { + + @Test + void testStringExpression() throws InterruptedException, ExecutionException { + try (WorkflowApplication app = WorkflowApplication.builder().build()) { + Workflow workflow = + new Workflow() + .withDocument( + new Document().withNamespace("test").withName("testString").withVersion("1.0")) + .withDo( + List.of( + new TaskItem( + "doNothing", + new Task() + .withWaitTask( + new WaitTask() + .withWait( + new TimeoutAfter() + .withDurationInline( + new DurationInline().withMilliseconds(10))))))) + .withOutput( + new Output() + .withAs( + new OutputAsFunction().withFunction(JavaFunctions::addJavieritoString))); + + assertThat( + app.workflowDefinition(workflow) + .instance("Francisco") + .start() + .get() + .asText() + .orElseThrow()) + .isEqualTo("Francisco Javierito"); + } + } + + @Test + void testMapExpression() throws InterruptedException, ExecutionException { + try (WorkflowApplication app = WorkflowApplication.builder().build()) { + Workflow workflow = + new Workflow() + .withDocument( + new Document().withNamespace("test").withName("testMap").withVersion("1.0")) + .withDo( + List.of( + new TaskItem( + "javierito", + new Task() + .withSetTask( + new SetTask() + .withSet( + new Set() + .withSetTaskConfiguration( + new SetTaskConfiguration() + .withAdditionalProperty("name", "Francisco"))) + .withOutput( + new Output() + .withAs( + new OutputAsFunction() + .withFunction( + JavaFunctions::addJavierito))))))); + assertThat( + app.workflowDefinition(workflow) + .instance(Map.of()) + .start() + .get() + .asMap() + .map(m -> m.get("name")) + .orElseThrow()) + .isEqualTo("Francisco Javierito"); + } + } + + @Test + void testStringPOJOExpression() throws InterruptedException, ExecutionException { + try (WorkflowApplication app = WorkflowApplication.builder().build()) { + Workflow workflow = + new Workflow() + .withDocument( + new Document().withNamespace("test").withName("testPojo").withVersion("1.0")) + .withDo( + List.of( + new TaskItem( + "doNothing", + new Task() + .withWaitTask( + new WaitTask() + .withWait( + new TimeoutAfter() + .withDurationInline( + new DurationInline().withMilliseconds(10))))))) + .withOutput( + new Output() + .withAs(new OutputAsFunction().withFunction(JavaFunctions::personPojo))); + + assertThat( + app.workflowDefinition(workflow) + .instance("Francisco") + .start() + .get() + .as(Person.class) + .orElseThrow() + .name()) + .isEqualTo("Francisco Javierito"); + } + } + + @Test + void testPOJOStringExpression() throws InterruptedException, ExecutionException { + try (WorkflowApplication app = WorkflowApplication.builder().build()) { + Workflow workflow = + new Workflow() + .withDocument( + new Document().withNamespace("test").withName("testPojo").withVersion("1.0")) + .withDo( + List.of( + new TaskItem( + "doNothing", + new Task() + .withWaitTask( + new WaitTask() + .withWait( + new TimeoutAfter() + .withDurationInline( + new DurationInline().withMilliseconds(10))))))) + .withOutput( + new Output().withAs(new OutputAsFunction().withFunction(JavaFunctions::getName))); + + assertThat( + app.workflowDefinition(workflow) + .instance(new Person("Francisco", 33)) + .start() + .get() + .asText() + .orElseThrow()) + .isEqualTo("Francisco Javierito"); + } + } +} diff --git a/api/src/main/java/io/serverlessworkflow/api/interfaces/Extension.java b/experimental/lambda/src/test/java/io/serverless/workflow/impl/Person.java similarity index 83% rename from api/src/main/java/io/serverlessworkflow/api/interfaces/Extension.java rename to experimental/lambda/src/test/java/io/serverless/workflow/impl/Person.java index 2f1e00f3..9594c285 100644 --- a/api/src/main/java/io/serverlessworkflow/api/interfaces/Extension.java +++ b/experimental/lambda/src/test/java/io/serverless/workflow/impl/Person.java @@ -1,21 +1,18 @@ /* * 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.interfaces; +package io.serverless.workflow.impl; -public interface Extension { - String getExtensionId(); -} \ No newline at end of file +record Person(String name, int age) {} diff --git a/experimental/pom.xml b/experimental/pom.xml new file mode 100644 index 00000000..a8dbc874 --- /dev/null +++ b/experimental/pom.xml @@ -0,0 +1,46 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-parent + 8.0.0-SNAPSHOT + + serverlessworkflow-experimental + pom + Serverless Workflow :: Experimental + + + + io.serverlessworkflow + serverlessworkflow-impl-core + ${project.version} + + + io.serverlessworkflow + serverlessworkflow-types + ${project.version} + + + io.serverlessworkflow + serverlessworkflow-experimental-lambda + ${project.version} + + + io.serverlessworkflow + serverlessworkflow-experimental-types + ${project.version} + + + io.serverlessworkflow + serverlessworkflow-fluent-func + ${project.version} + + + + + types + lambda + agentic + lambda-fluent + + \ No newline at end of file diff --git a/experimental/types/pom.xml b/experimental/types/pom.xml new file mode 100644 index 00000000..3165f28d --- /dev/null +++ b/experimental/types/pom.xml @@ -0,0 +1,16 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-experimental + 8.0.0-SNAPSHOT + + serverlessworkflow-experimental-types + Serverless Workflow :: Experimental:: Types + + + io.serverlessworkflow + serverlessworkflow-types + + + \ No newline at end of file diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallJava.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallJava.java new file mode 100644 index 00000000..6423fdae --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallJava.java @@ -0,0 +1,131 @@ +/* + * 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.types.func; + +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.impl.expressions.LoopFunction; +import io.serverlessworkflow.impl.expressions.LoopFunctionIndex; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; + +public abstract class CallJava extends TaskBase { + + private static final long serialVersionUID = 1L; + + public static CallJava consumer(Consumer consumer) { + return new CallJavaConsumer<>(consumer); + } + + public static CallJavaFunction function(Function function) { + return new CallJavaFunction<>(function, Optional.empty()); + } + + public static CallJavaFunction function( + Function function, Class inputClass) { + return new CallJavaFunction<>(function, Optional.ofNullable(inputClass)); + } + + public static CallJava loopFunction( + LoopFunctionIndex function, String varName, String indexName) { + return new CallJavaLoopFunctionIndex<>(function, varName, indexName); + } + + public static CallJava loopFunction(LoopFunction function, String varName) { + return new CallJavaLoopFunction<>(function, varName); + } + + public static class CallJavaConsumer extends CallJava { + + private static final long serialVersionUID = 1L; + private Consumer consumer; + + public CallJavaConsumer(Consumer consumer) { + this.consumer = consumer; + } + + public Consumer consumer() { + return consumer; + } + } + + public static class CallJavaFunction extends CallJava { + + private static final long serialVersionUID = 1L; + private Function function; + private Optional> inputClass; + + public CallJavaFunction(Function function, Optional> inputClass) { + this.function = function; + this.inputClass = inputClass; + } + + public Function function() { + return function; + } + + public Optional> inputClass() { + return inputClass; + } + } + + public static class CallJavaLoopFunction extends CallJava { + + private static final long serialVersionUID = 1L; + private LoopFunction function; + private String varName; + + public CallJavaLoopFunction(LoopFunction function, String varName) { + this.function = function; + this.varName = varName; + } + + public LoopFunction function() { + return function; + } + + public String varName() { + return varName; + } + } + + public static class CallJavaLoopFunctionIndex extends CallJava { + + private static final long serialVersionUID = 1L; + private final LoopFunctionIndex function; + private final String varName; + private final String indexName; + + public CallJavaLoopFunctionIndex( + LoopFunctionIndex function, String varName, String indexName) { + this.function = function; + this.varName = varName; + this.indexName = indexName; + } + + public LoopFunctionIndex function() { + return function; + } + + public String varName() { + return varName; + } + + public String indexName() { + return indexName; + } + } +} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallTaskJava.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallTaskJava.java new file mode 100644 index 00000000..cde6281f --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/CallTaskJava.java @@ -0,0 +1,36 @@ +/* + * 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.types.func; + +import io.serverlessworkflow.api.types.CallTask; + +public class CallTaskJava extends CallTask { + + private CallJava callJava; + + public CallTaskJava(CallJava callJava) { + this.callJava = callJava; + } + + public CallJava getCallJava() { + return callJava; + } + + @Override + public Object get() { + return callJava != null ? callJava : super.get(); + } +} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/ExportAsFunction.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/ExportAsFunction.java new file mode 100644 index 00000000..45a81892 --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/ExportAsFunction.java @@ -0,0 +1,34 @@ +/* + * 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.types.func; + +import io.serverlessworkflow.api.types.ExportAs; +import java.util.Objects; +import java.util.function.Function; + +public class ExportAsFunction extends ExportAs { + + public ExportAs withFunction(Function value) { + setObject(value); + return this; + } + + public ExportAs withFunction(Function value, Class argClass) { + Objects.requireNonNull(argClass); + setObject(new TypedFunction<>(value, argClass)); + return this; + } +} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/ForTaskFunction.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/ForTaskFunction.java new file mode 100644 index 00000000..eb4ac716 --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/ForTaskFunction.java @@ -0,0 +1,105 @@ +/* + * 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.types.func; + +import io.serverlessworkflow.api.types.ForTask; +import io.serverlessworkflow.impl.expressions.LoopPredicate; +import io.serverlessworkflow.impl.expressions.LoopPredicateIndex; +import java.util.Collection; +import java.util.Optional; +import java.util.function.Function; + +public class ForTaskFunction extends ForTask { + + private static final long serialVersionUID = 1L; + private LoopPredicateIndex whilePredicate; + private Optional> whileClass; + private Optional> itemClass; + private Optional> forClass; + private Function> collection; + + public ForTaskFunction withWhile(LoopPredicate whilePredicate) { + return withWhile(toPredicateIndex(whilePredicate)); + } + + public ForTaskFunction withWhile(LoopPredicate whilePredicate, Class modelClass) { + return withWhile(toPredicateIndex(whilePredicate), modelClass); + } + + public ForTaskFunction withWhile( + LoopPredicate whilePredicate, Class modelClass, Class itemClass) { + return withWhile(toPredicateIndex(whilePredicate), modelClass, itemClass); + } + + private LoopPredicateIndex toPredicateIndex(LoopPredicate whilePredicate) { + return (model, item, index) -> whilePredicate.test(model, item); + } + + public ForTaskFunction withWhile(LoopPredicateIndex whilePredicate) { + return withWhile(whilePredicate, Optional.empty(), Optional.empty()); + } + + public ForTaskFunction withWhile( + LoopPredicateIndex whilePredicate, Class modelClass) { + return withWhile(whilePredicate, Optional.ofNullable(modelClass), Optional.empty()); + } + + public ForTaskFunction withWhile( + LoopPredicateIndex whilePredicate, Class modelClass, Class itemClass) { + return withWhile(whilePredicate, Optional.ofNullable(modelClass), Optional.of(itemClass)); + } + + private ForTaskFunction withWhile( + LoopPredicateIndex whilePredicate, + Optional> modelClass, + Optional> itemClass) { + this.whilePredicate = whilePredicate; + this.whileClass = modelClass; + this.itemClass = itemClass; + return this; + } + + public ForTaskFunction withCollection(Function> collection) { + return withCollection(collection, null); + } + + public ForTaskFunction withCollection( + Function> collection, Class colArgClass) { + this.collection = collection; + this.forClass = Optional.ofNullable(colArgClass); + return this; + } + + public LoopPredicateIndex getWhilePredicate() { + return whilePredicate; + } + + public Optional> getWhileClass() { + return whileClass; + } + + public Optional> getForClass() { + return forClass; + } + + public Optional> getItemClass() { + return itemClass; + } + + public Function> getCollection() { + return collection; + } +} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/InputFromFunction.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/InputFromFunction.java new file mode 100644 index 00000000..521dca87 --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/InputFromFunction.java @@ -0,0 +1,34 @@ +/* + * 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.types.func; + +import io.serverlessworkflow.api.types.InputFrom; +import java.util.Objects; +import java.util.function.Function; + +public class InputFromFunction extends InputFrom { + + public InputFrom withFunction(Function value) { + setObject(value); + return this; + } + + public InputFrom withFunction(Function value, Class argClass) { + Objects.requireNonNull(argClass); + setObject(new TypedFunction<>(value, argClass)); + return this; + } +} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/OutputAsFunction.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/OutputAsFunction.java new file mode 100644 index 00000000..8d2d6dc5 --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/OutputAsFunction.java @@ -0,0 +1,34 @@ +/* + * 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.types.func; + +import io.serverlessworkflow.api.types.OutputAs; +import java.util.Objects; +import java.util.function.Function; + +public class OutputAsFunction extends OutputAs { + + public OutputAs withFunction(Function value) { + setObject(value); + return this; + } + + public OutputAs withFunction(Function value, Class argClass) { + Objects.requireNonNull(argClass); + setObject(new TypedFunction<>(value, argClass)); + return this; + } +} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/SwitchCaseFunction.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/SwitchCaseFunction.java new file mode 100644 index 00000000..01813c5d --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/SwitchCaseFunction.java @@ -0,0 +1,47 @@ +/* + * 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.types.func; + +import io.serverlessworkflow.api.types.SwitchCase; +import java.util.Optional; +import java.util.function.Predicate; + +public class SwitchCaseFunction extends SwitchCase { + + private static final long serialVersionUID = 1L; + private Predicate predicate; + private Optional> predicateClass; + + public SwitchCaseFunction withPredicate(Predicate predicate) { + this.predicate = predicate; + this.predicateClass = Optional.empty(); + return this; + } + + public SwitchCaseFunction withPredicate(Predicate predicate, Class predicateClass) { + this.predicate = predicate; + this.predicateClass = Optional.ofNullable(predicateClass); + return this; + } + + public Predicate predicate() { + return predicate; + } + + public Optional> predicateClass() { + return predicateClass; + } +} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/TypedFunction.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/TypedFunction.java new file mode 100644 index 00000000..c38bbb92 --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/TypedFunction.java @@ -0,0 +1,20 @@ +/* + * 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.types.func; + +import java.util.function.Function; + +public record TypedFunction(Function function, Class argClass) {} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/TypedPredicate.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/TypedPredicate.java new file mode 100644 index 00000000..26c0893e --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/func/TypedPredicate.java @@ -0,0 +1,20 @@ +/* + * 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.types.func; + +import java.util.function.Predicate; + +public record TypedPredicate(Predicate pred, Class argClass) {} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/impl/expressions/LoopFunction.java b/experimental/types/src/main/java/io/serverlessworkflow/impl/expressions/LoopFunction.java new file mode 100644 index 00000000..6e23b97b --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/impl/expressions/LoopFunction.java @@ -0,0 +1,21 @@ +/* + * 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.impl.expressions; + +import java.util.function.BiFunction; + +@FunctionalInterface +public interface LoopFunction extends BiFunction {} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/impl/expressions/LoopFunctionIndex.java b/experimental/types/src/main/java/io/serverlessworkflow/impl/expressions/LoopFunctionIndex.java new file mode 100644 index 00000000..783092dd --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/impl/expressions/LoopFunctionIndex.java @@ -0,0 +1,21 @@ +/* + * 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.impl.expressions; + +@FunctionalInterface +public interface LoopFunctionIndex { + R apply(T model, V item, Integer index); +} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/impl/expressions/LoopPredicate.java b/experimental/types/src/main/java/io/serverlessworkflow/impl/expressions/LoopPredicate.java new file mode 100644 index 00000000..ecbeda77 --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/impl/expressions/LoopPredicate.java @@ -0,0 +1,21 @@ +/* + * 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.impl.expressions; + +import java.util.function.BiPredicate; + +@FunctionalInterface +public interface LoopPredicate extends BiPredicate {} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/impl/expressions/LoopPredicateIndex.java b/experimental/types/src/main/java/io/serverlessworkflow/impl/expressions/LoopPredicateIndex.java new file mode 100644 index 00000000..dc897683 --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/impl/expressions/LoopPredicateIndex.java @@ -0,0 +1,21 @@ +/* + * 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.impl.expressions; + +@FunctionalInterface +public interface LoopPredicateIndex { + boolean test(T model, V item, Integer index); +} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/impl/expressions/TaskMetadataKeys.java b/experimental/types/src/main/java/io/serverlessworkflow/impl/expressions/TaskMetadataKeys.java new file mode 100644 index 00000000..879dc5ea --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/impl/expressions/TaskMetadataKeys.java @@ -0,0 +1,22 @@ +/* + * 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.impl.expressions; + +public final class TaskMetadataKeys { + + /** Metadata entry name for the DSL’s “when”/“if” predicate. */ + public static final String IF_PREDICATE = "if_predicate"; +} diff --git a/fluent/README.md b/fluent/README.md new file mode 100644 index 00000000..6e4d7bb0 --- /dev/null +++ b/fluent/README.md @@ -0,0 +1,204 @@ +# CNCF Serverless Workflow SDK Java — Fluent DSL + +> A programmatic, type‑safe Java API for building and running Serverless Workflows (and agentic workflows) without writing YAML. + +--- + +## 📦 Modules + +| Module | Purpose | +| -------------- | --------------------------------------------------------------------------------------------- | +| **spec** | Core DSL implementing the [Serverless Workflow Specification](https://github.com/serverlessworkflow/specification). Purely compliant fluent API. | +| **func** | Java‑centric “functional” DSL on top of **spec**: adds `Function<>`/`Predicate<>` support, `callFn` for Java method calls, and richer flow controls. | +| **agentic** | **Experimental** proof‑of‑concept DSL built on **func** for LangChain4j agentic workflows: `agent`, `sequence`, `loop`, `parallel`, etc. | + +--- + +## 🔧 Getting Started + +Add the modules you need to your Maven `pom.xml` (replace versions as appropriate): + +```xml + + + io.serverlessworkflow + serverlessworkflow-fluent-spec + ${version.io.serverlessworkflow} + + + io.serverlessworkflow + serverlessworkflow-fluent-func + ${version.io.serverlessworkflow} + + + io.serverlessworkflow + serverlessworkflow-fluent-agentic + ${version.io.serverlessworkflow} + +``` + +--- + +## 📖 Module Reference + +### 1. Spec Fluent + +Fully compliant with the CNCF Serverless Workflow spec.\ +Use it when you want a 1:1 mapping of the YAML DSL in Java. + +```java +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.fluent.spec.WorkflowBuilder; + +Workflow wf = WorkflowBuilder + .workflow("flowDo") + .tasks(tasks -> + tasks + .set("initCtx", "$.foo = 'bar'") + .forEach("item", f -> f + .each("item") + .at("$.list") + ) + ) + .build(); +``` + +> [!NOTE] +> We rename reserved keywords (`for`, `do`, `if`, `while`, `switch`, `try`) to safe identifiers (`forEach`, `tasks`, `when`, etc.). + +--- + +### 2. Func Fluent + +A Java‑first DSL that builds on **spec**, adding: + +- `callFn`: invoke arbitrary Java `Function<>` handlers +- `Predicate<>` **guards** via `when(Predicate)` +- Built‑in `Function`/`Predicate` support instead of JQ expressions + +```java +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.fluent.func.FuncWorkflowBuilder; + +Workflow wf = FuncWorkflowBuilder + .workflow("callJavaFlow") + .tasks(tasks -> + tasks.callFn("invokeHandler", call -> call + // e.g. call.className("com.acme.Handler") + // .method("handle") + // .arg("key", "value") + .function(ctx -> { + // your code here + }) + ) + ) + .build(); +``` + +> [!WARNING] +> The **func** DSL is *not* spec‑compliant. It adds Java‑specific tasks and control‑flow extensions for in‑JVM execution. + +--- + +### 3. Agentic Fluent *(Experimental)* + +Built on **func** for LangChain4j agentic workflows. Adds: + +- `agent(instance)`: invoke a LangChain4j agent +- `sequence(...)`: run agents in order +- `loop(cfg)`: retry or repeated agent calls +- `parallel(...)`: fork agent calls concurrently + +```java +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.fluent.agentic.AgentWorkflowBuilder; + +var scorer = AgentsUtils.newMovieExpert(); +var editor = AgentsUtils.newMovieExpert(); + +Workflow wf = AgentWorkflowBuilder + .workflow("retryFlow") + .tasks(tasks -> tasks.loop( + "reviewLoop", + loop -> loop + .maxIterations(5) + .exitCondition(c -> c.readState("score", 0).doubleValue() > 0.75) + .subAgents("reviewer", scorer, editor) + )) + .build(); +``` + +--- + +## 🚀 Real‑World Example: Order Fulfillment + +```java +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.fluent.agentic.AgentWorkflowBuilder; +import java.util.function.Predicate; + +public class OrderFulfillment { + + static class InventoryAgent { /* … */ } + static class NotificationAgent { /* … */ } + static class ShippingAgent { /* … */ } + + public Workflow buildWorkflow() { + + Predicate inventoryOk = state -> + Boolean.TRUE.equals(((java.util.Map) state).get("inventoryAvailable")); + + return AgentWorkflowBuilder + .workflow("OrderFulfillment") + .tasks(tasks -> tasks + + // 1. initialize state + .set("init", s -> s.expr("$.orderId = '.input.oriderId'")) + + // 2. check inventory + .agent("checkInventory", new InventoryAgent()) + + // 3. pull result into a flag + .set("inventoryAvailable", s -> s.expr("$.checkInventory.available")) + + // 4. retry until in stock (max 3 attempts) + .loop("retryIfOutOfStock", loop -> loop + .maxIterations(3) + .exitCondition(inventoryOk) + .subAgents("inventoryChecker", new InventoryAgent()) + ) + + // 5. notify systems in parallel + .parallel("notifyAll", + new NotificationAgent(), + new ShippingAgent() + ) + + // 6. mark order complete + .set("complete", s -> s.expr("$.status = 'COMPLETED'")) + ) + .build(); + } +} +``` + +--- + +## 🛠️ Next Steps & Roadmap + +- **Error handling**: retries, back‑off, `onError` handlers +- **Timers & delays**: `wait`, per‑task `timeout` +- **Sub‑workflows** & composition: call one workflow from another +- **Event tasks**: `onEvent`, `sendEvent` +- **Human‑in‑the‑Loop**: approval/notification steps + +Contributions welcome! Check out our [CONTRIBUTING.md](../CONTRIBUTING.md) and join the CNCF Slack channel for **Serverless Workflow**. + +--- + +## 📜 License + +Apache 2.0 © Serverless Workflow Authors diff --git a/fluent/agentic/pom.xml b/fluent/agentic/pom.xml new file mode 100644 index 00000000..849bf747 --- /dev/null +++ b/fluent/agentic/pom.xml @@ -0,0 +1,68 @@ + + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-fluent + 8.0.0-SNAPSHOT + + + Serverless Workflow :: Fluent :: Agentic + serverlessworkflow-fluent-agentic + + + 17 + 17 + UTF-8 + + + + + io.serverlessworkflow + serverlessworkflow-experimental-types + + + io.serverlessworkflow + serverlessworkflow-fluent-func + + + dev.langchain4j + langchain4j-agentic + + + org.slf4j + slf4j-simple + test + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.mockito + mockito-core + test + + + org.assertj + assertj-core + + + dev.langchain4j + langchain4j-ollama + test + 1.2.0-SNAPSHOT + + + io.serverlessworkflow + serverlessworkflow-experimental-agentic + ${project.version} + test + + + + \ No newline at end of file diff --git a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentAdapters.java b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentAdapters.java new file mode 100644 index 00000000..ebcde632 --- /dev/null +++ b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentAdapters.java @@ -0,0 +1,44 @@ +/* + * 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.fluent.agentic; + +import static dev.langchain4j.agentic.internal.AgentUtil.agentsToExecutors; + +import dev.langchain4j.agentic.cognisphere.Cognisphere; +import dev.langchain4j.agentic.internal.AgentExecutor; +import dev.langchain4j.agentic.internal.AgentInstance; +import io.serverlessworkflow.impl.expressions.LoopPredicateIndex; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public final class AgentAdapters { + + private AgentAdapters() {} + + public static List toExecutors(Object... agents) { + return agentsToExecutors(Stream.of(agents).map(AgentInstance.class::cast).toArray()); + } + + public static Function toFunction(AgentExecutor exec) { + return exec::invoke; + } + + public static LoopPredicateIndex toWhile(Predicate exit) { + return (model, item, idx) -> !exit.test(model); + } +} diff --git a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentDoTaskBuilder.java b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentDoTaskBuilder.java new file mode 100644 index 00000000..a69a4bd1 --- /dev/null +++ b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentDoTaskBuilder.java @@ -0,0 +1,108 @@ +/* + * 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.fluent.agentic; + +import io.serverlessworkflow.fluent.agentic.spi.AgentDoFluent; +import io.serverlessworkflow.fluent.func.FuncCallTaskBuilder; +import io.serverlessworkflow.fluent.func.FuncEmitTaskBuilder; +import io.serverlessworkflow.fluent.func.FuncForTaskBuilder; +import io.serverlessworkflow.fluent.func.FuncForkTaskBuilder; +import io.serverlessworkflow.fluent.func.FuncSetTaskBuilder; +import io.serverlessworkflow.fluent.func.FuncSwitchTaskBuilder; +import io.serverlessworkflow.fluent.func.spi.ConditionalTaskBuilder; +import io.serverlessworkflow.fluent.spec.BaseDoTaskBuilder; +import java.util.function.Consumer; + +public class AgentDoTaskBuilder + extends BaseDoTaskBuilder + implements ConditionalTaskBuilder, AgentDoFluent { + + public AgentDoTaskBuilder() { + super(new AgentTaskItemListBuilder()); + } + + @Override + protected AgentDoTaskBuilder self() { + return this; + } + + @Override + public AgentDoTaskBuilder agent(String name, Object agent) { + this.listBuilder().agent(name, agent); + return self(); + } + + @Override + public AgentDoTaskBuilder sequence(String name, Object... agents) { + this.listBuilder().sequence(name, agents); + return self(); + } + + @Override + public AgentDoTaskBuilder loop(String name, Consumer builder) { + this.listBuilder().loop(name, builder); + return self(); + } + + @Override + public AgentDoTaskBuilder parallel(String name, Object... agents) { + this.listBuilder().parallel(name, agents); + return self(); + } + + @Override + public AgentDoTaskBuilder callFn(String name, Consumer cfg) { + this.listBuilder().callFn(name, cfg); + return self(); + } + + @Override + public AgentDoTaskBuilder emit(String name, Consumer itemsConfigurer) { + this.listBuilder().emit(name, itemsConfigurer); + return self(); + } + + @Override + public AgentDoTaskBuilder forEach(String name, Consumer itemsConfigurer) { + this.listBuilder().forEach(name, itemsConfigurer); + return self(); + } + + @Override + public AgentDoTaskBuilder fork(String name, Consumer itemsConfigurer) { + this.listBuilder().fork(name, itemsConfigurer); + return self(); + } + + @Override + public AgentDoTaskBuilder set(String name, Consumer itemsConfigurer) { + this.listBuilder().set(name, itemsConfigurer); + return self(); + } + + @Override + public AgentDoTaskBuilder set(String name, String expr) { + this.listBuilder().set(name, expr); + return self(); + } + + @Override + public AgentDoTaskBuilder switchCase( + String name, Consumer itemsConfigurer) { + this.listBuilder().switchCase(name, itemsConfigurer); + return self(); + } +} diff --git a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentTaskItemListBuilder.java b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentTaskItemListBuilder.java new file mode 100644 index 00000000..60329e43 --- /dev/null +++ b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentTaskItemListBuilder.java @@ -0,0 +1,138 @@ +/* + * 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.fluent.agentic; + +import dev.langchain4j.agentic.cognisphere.Cognisphere; +import dev.langchain4j.agentic.internal.AgentExecutor; +import io.serverlessworkflow.api.types.Task; +import io.serverlessworkflow.api.types.TaskItem; +import io.serverlessworkflow.fluent.agentic.spi.AgentDoFluent; +import io.serverlessworkflow.fluent.func.FuncCallTaskBuilder; +import io.serverlessworkflow.fluent.func.FuncEmitTaskBuilder; +import io.serverlessworkflow.fluent.func.FuncForTaskBuilder; +import io.serverlessworkflow.fluent.func.FuncForkTaskBuilder; +import io.serverlessworkflow.fluent.func.FuncSetTaskBuilder; +import io.serverlessworkflow.fluent.func.FuncSwitchTaskBuilder; +import io.serverlessworkflow.fluent.func.FuncTaskItemListBuilder; +import io.serverlessworkflow.fluent.spec.BaseTaskItemListBuilder; +import java.util.List; +import java.util.function.Consumer; + +public class AgentTaskItemListBuilder extends BaseTaskItemListBuilder + implements AgentDoFluent { + + private final FuncTaskItemListBuilder delegate; + + public AgentTaskItemListBuilder() { + super(); + this.delegate = new FuncTaskItemListBuilder(super.mutableList()); + } + + @Override + protected AgentTaskItemListBuilder self() { + return this; + } + + @Override + protected AgentTaskItemListBuilder newItemListBuilder() { + return new AgentTaskItemListBuilder(); + } + + @Override + public AgentTaskItemListBuilder agent(String name, Object agent) { + AgentAdapters.toExecutors(agent) + .forEach( + exec -> + this.delegate.callFn( + name, fn -> fn.function(AgentAdapters.toFunction(exec), Cognisphere.class))); + return self(); + } + + @Override + public AgentTaskItemListBuilder sequence(String name, Object... agents) { + for (int i = 0; i < agents.length; i++) { + agent(name + "-" + i, agents[i]); + } + return self(); + } + + @Override + public AgentTaskItemListBuilder loop(String name, Consumer consumer) { + final LoopAgentsBuilder builder = new LoopAgentsBuilder(); + consumer.accept(builder); + this.addTaskItem(new TaskItem(name, new Task().withForTask(builder.build()))); + return self(); + } + + @Override + public AgentTaskItemListBuilder parallel(String name, Object... agents) { + this.delegate.fork( + name, + fork -> { + List execs = AgentAdapters.toExecutors(agents); + for (int i = 0; i < execs.size(); i++) { + AgentExecutor ex = execs.get(i); + fork.branch( + "branch-" + i + "-" + name, AgentAdapters.toFunction(ex), Cognisphere.class); + } + }); + return self(); + } + + @Override + public AgentTaskItemListBuilder callFn(String name, Consumer cfg) { + this.delegate.callFn(name, cfg); + return self(); + } + + @Override + public AgentTaskItemListBuilder emit(String name, Consumer itemsConfigurer) { + this.delegate.emit(name, itemsConfigurer); + return self(); + } + + @Override + public AgentTaskItemListBuilder forEach( + String name, Consumer itemsConfigurer) { + this.delegate.forEach(name, itemsConfigurer); + return self(); + } + + @Override + public AgentTaskItemListBuilder fork(String name, Consumer itemsConfigurer) { + this.delegate.fork(name, itemsConfigurer); + return self(); + } + + @Override + public AgentTaskItemListBuilder set(String name, Consumer itemsConfigurer) { + this.delegate.set(name, itemsConfigurer); + return self(); + } + + @Override + public AgentTaskItemListBuilder set(String name, String expr) { + this.delegate.set(name, expr); + return self(); + } + + @Override + public AgentTaskItemListBuilder switchCase( + String name, Consumer itemsConfigurer) { + this.delegate.switchCase(name, itemsConfigurer); + return self(); + } +} diff --git a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentWorkflowBuilder.java b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentWorkflowBuilder.java new file mode 100644 index 00000000..bd943ebc --- /dev/null +++ b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/AgentWorkflowBuilder.java @@ -0,0 +1,51 @@ +/* + * 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.fluent.agentic; + +import io.serverlessworkflow.fluent.spec.BaseWorkflowBuilder; +import java.util.UUID; + +public class AgentWorkflowBuilder + extends BaseWorkflowBuilder< + AgentWorkflowBuilder, AgentDoTaskBuilder, AgentTaskItemListBuilder> { + + AgentWorkflowBuilder(final String name, final String namespace, final String version) { + super(name, namespace, version); + } + + public static AgentWorkflowBuilder workflow() { + return new AgentWorkflowBuilder( + UUID.randomUUID().toString(), DEFAULT_NAMESPACE, DEFAULT_VERSION); + } + + public static AgentWorkflowBuilder workflow(String name) { + return new AgentWorkflowBuilder(name, DEFAULT_NAMESPACE, DEFAULT_VERSION); + } + + public static AgentWorkflowBuilder workflow(String name, String ns) { + return new AgentWorkflowBuilder(name, ns, DEFAULT_VERSION); + } + + @Override + protected AgentDoTaskBuilder newDo() { + return new AgentDoTaskBuilder(); + } + + @Override + protected AgentWorkflowBuilder self() { + return this; + } +} diff --git a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/LoopAgentsBuilder.java b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/LoopAgentsBuilder.java new file mode 100644 index 00000000..f98089c8 --- /dev/null +++ b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/LoopAgentsBuilder.java @@ -0,0 +1,72 @@ +/* + * 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.fluent.agentic; + +import dev.langchain4j.agentic.cognisphere.Cognisphere; +import dev.langchain4j.agentic.internal.AgentExecutor; +import io.serverlessworkflow.api.types.ForTaskConfiguration; +import io.serverlessworkflow.api.types.func.ForTaskFunction; +import io.serverlessworkflow.fluent.func.FuncTaskItemListBuilder; +import java.util.List; +import java.util.UUID; +import java.util.function.ObjIntConsumer; +import java.util.function.Predicate; +import java.util.stream.IntStream; + +public class LoopAgentsBuilder { + + private final FuncTaskItemListBuilder funcDelegate; + private final ForTaskFunction forTask; + + LoopAgentsBuilder() { + this.forTask = new ForTaskFunction(); + this.forTask.setFor(new ForTaskConfiguration()); + this.funcDelegate = new FuncTaskItemListBuilder(); + } + + private static void forEachIndexed(List list, ObjIntConsumer consumer) { + IntStream.range(0, list.size()).forEach(i -> consumer.accept(list.get(i), i)); + } + + public LoopAgentsBuilder subAgents(String baseName, Object... agents) { + List execs = AgentAdapters.toExecutors(agents); + forEachIndexed( + execs, + (exec, idx) -> + funcDelegate.callFn( + baseName + "-" + idx, fn -> fn.function(AgentAdapters.toFunction(exec)))); + return this; + } + + public LoopAgentsBuilder subAgents(Object... agents) { + return this.subAgents("agent-" + UUID.randomUUID(), agents); + } + + public LoopAgentsBuilder maxIterations(int maxIterations) { + this.forTask.withCollection(ignored -> IntStream.range(0, maxIterations).boxed().toList()); + return this; + } + + public LoopAgentsBuilder exitCondition(Predicate exitCondition) { + this.forTask.withWhile(AgentAdapters.toWhile(exitCondition), Cognisphere.class); + return this; + } + + public ForTaskFunction build() { + this.forTask.setDo(this.funcDelegate.build()); + return this.forTask; + } +} diff --git a/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/spi/AgentDoFluent.java b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/spi/AgentDoFluent.java new file mode 100644 index 00000000..c23f8ef3 --- /dev/null +++ b/fluent/agentic/src/main/java/io/serverlessworkflow/fluent/agentic/spi/AgentDoFluent.java @@ -0,0 +1,48 @@ +/* + * 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.fluent.agentic.spi; + +import io.serverlessworkflow.fluent.agentic.LoopAgentsBuilder; +import io.serverlessworkflow.fluent.func.spi.FuncDoFluent; +import java.util.UUID; +import java.util.function.Consumer; + +public interface AgentDoFluent> extends FuncDoFluent { + + SELF agent(String name, Object agent); + + default SELF agent(Object agent) { + return agent(UUID.randomUUID().toString(), agent); + } + + SELF sequence(String name, Object... agents); + + default SELF sequence(Object... agents) { + return sequence("seq-" + UUID.randomUUID(), agents); + } + + SELF loop(String name, Consumer builder); + + default SELF loop(Consumer builder) { + return loop("loop-" + UUID.randomUUID(), builder); + } + + SELF parallel(String name, Object... agents); + + default SELF parallel(Object... agents) { + return parallel("par-" + UUID.randomUUID(), agents); + } +} diff --git a/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentDslWorkflowTest.java b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentDslWorkflowTest.java new file mode 100644 index 00000000..84a348b6 --- /dev/null +++ b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentDslWorkflowTest.java @@ -0,0 +1,138 @@ +/* + * 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.fluent.agentic; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.serverlessworkflow.api.types.TaskItem; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.api.types.func.CallTaskJava; +import io.serverlessworkflow.api.types.func.ForTaskFunction; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class AgentDslWorkflowTest { + + @Test + @DisplayName("Sequential agents via DSL.sequence(...)") + void dslSequentialAgents() { + var a1 = AgentsUtils.newMovieExpert(); + var a2 = AgentsUtils.newMovieExpert(); + var a3 = AgentsUtils.newMovieExpert(); + + Workflow wf = + AgentWorkflowBuilder.workflow("seqFlow") + .tasks(tasks -> tasks.sequence("process", a1, a2, a3)) + .build(); + + List items = wf.getDo(); + assertThat(items).hasSize(3); + // names should be process-0, process-1, process-2 + assertThat(items.get(0).getName()).isEqualTo("process-0"); + assertThat(items.get(1).getName()).isEqualTo("process-1"); + assertThat(items.get(2).getName()).isEqualTo("process-2"); + // each is a CallTaskJava under the hood + items.forEach(it -> assertThat(it.getTask().getCallTask()).isInstanceOf(CallTaskJava.class)); + } + + @Test + @DisplayName("Bare Java‑bean call via DSL.callFn(...)") + void dslCallFnBare() { + Workflow wf = + AgentWorkflowBuilder.workflow("beanCall") + .tasks(tasks -> tasks.callFn("plainCall", fn -> fn.function(ctx -> "pong"))) + .build(); + + List items = wf.getDo(); + assertThat(items).hasSize(1); + TaskItem ti = items.get(0); + assertThat(ti.getName()).isEqualTo("plainCall"); + assertThat(ti.getTask().getCallTask()).isInstanceOf(CallTaskJava.class); + } + + @Test + void dslLoopAgents() { + var scorer = AgentsUtils.newMovieExpert(); // re‑using MovieExpert as a stand‑in for scoring + var editor = AgentsUtils.newMovieExpert(); // likewise + + Workflow wf = + AgentWorkflowBuilder.workflow("retryFlow") + .tasks( + tasks -> + tasks.loop( + "reviewLoop", + loop -> + loop.maxIterations(5) + .exitCondition(c -> c.readState("score", 0).doubleValue() > 0.75) + .subAgents("reviewer", scorer, editor))) + .build(); + + List items = wf.getDo(); + assertThat(items).hasSize(1); + + var fn = (ForTaskFunction) items.get(0).getTask().getForTask(); + assertThat(fn.getDo()).isNotNull(); + assertThat(fn.getDo()).hasSize(2); + fn.getDo() + .forEach(si -> assertThat(si.getTask().getCallTask()).isInstanceOf(CallTaskJava.class)); + } + + @Test + void dslParallelAgents() { + var a1 = AgentsUtils.newMovieExpert(); + var a2 = AgentsUtils.newMovieExpert(); + + Workflow wf = + AgentWorkflowBuilder.workflow("forkFlow") + .tasks(tasks -> tasks.parallel("fanout", a1, a2)) + .build(); + + List items = wf.getDo(); + assertThat(items).hasSize(1); + + var fork = items.get(0).getTask().getForkTask(); + // two branches created + assertThat(fork.getFork().getBranches()).hasSize(2); + // branch names follow "branch-{index}-{name}" + assertThat(fork.getFork().getBranches().get(0).getName()).isEqualTo("branch-0-fanout"); + assertThat(fork.getFork().getBranches().get(1).getName()).isEqualTo("branch-1-fanout"); + } + + @Test + void dslMixSpecAndAgent() { + var agent = AgentsUtils.newMovieExpert(); + + Workflow wf = + AgentWorkflowBuilder.workflow("mixedFlow") + .tasks( + tasks -> + tasks + .set("init", s -> s.expr("$.initialized = true")) + .agent("callAgent", agent) + .set("done", "$.status = 'OK'")) + .build(); + + List items = wf.getDo(); + assertThat(items).hasSize(3); + // init is a SetTask + assertThat(items.get(0).getTask().getSetTask()).isNotNull(); + // agent call becomes a CallTaskJava + assertThat(items.get(1).getTask().getCallTask()).isInstanceOf(CallTaskJava.class); + // done is another SetTask + assertThat(items.get(2).getTask().getSetTask()).isNotNull(); + } +} diff --git a/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentTaskItemListBuilderTest.java b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentTaskItemListBuilderTest.java new file mode 100644 index 00000000..8b9585db --- /dev/null +++ b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentTaskItemListBuilderTest.java @@ -0,0 +1,86 @@ +/* + * 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.fluent.agentic; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.serverlessworkflow.api.types.Task; +import io.serverlessworkflow.api.types.TaskItem; +import io.serverlessworkflow.api.types.func.CallTaskJava; +import io.serverlessworkflow.api.types.func.ForTaskFunction; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** Structural tests for AgentTaskItemListBuilder. */ +class AgentTaskItemListBuilderTest { + + @Test + @DisplayName("agent(name,obj) adds a CallTaskJava-backed TaskItem") + void testAgentAddsCallTask() { + AgentTaskItemListBuilder b = new AgentTaskItemListBuilder(); + Agents.MovieExpert agent = AgentsUtils.newMovieExpert(); + + b.agent("my-agent", agent); + + List items = b.build(); + assertThat(items).hasSize(1); + TaskItem item = items.get(0); + assertThat(item.getName()).isEqualTo("my-agent"); + + Task task = item.getTask(); + assertThat(task.getCallTask()).isInstanceOf(CallTaskJava.class); + } + + @Test + @DisplayName("sequence(name, agents...) expands to N CallTask items, in order") + void testSequence() { + AgentTaskItemListBuilder b = new AgentTaskItemListBuilder(); + Agents.MovieExpert a1 = AgentsUtils.newMovieExpert(); + Agents.MovieExpert a2 = AgentsUtils.newMovieExpert(); + Agents.MovieExpert a3 = AgentsUtils.newMovieExpert(); + + b.sequence("seq", a1, a2, a3); + + List items = b.build(); + assertThat(items).hasSize(3); + assertThat(items.get(0).getName()).isEqualTo("seq-0"); + assertThat(items.get(1).getName()).isEqualTo("seq-1"); + assertThat(items.get(2).getName()).isEqualTo("seq-2"); + + // All must be call branche + items.forEach(it -> assertThat(it.getTask().getCallTask().get()).isNotNull()); + } + + @Test + @DisplayName("loop(name, builder) produces a ForTaskFunction with inner call branche") + void testLoop() { + AgentTaskItemListBuilder b = new AgentTaskItemListBuilder(); + Agents.MovieExpert scorer = AgentsUtils.newMovieExpert(); + Agents.MovieExpert editor = AgentsUtils.newMovieExpert(); + + b.loop("rev-loop", loop -> loop.subAgents("inner", scorer, editor)); + + List items = b.build(); + assertThat(items).hasSize(1); + + TaskItem loopItem = items.get(0); + ForTaskFunction forFn = (ForTaskFunction) loopItem.getTask().getForTask(); + assertThat(forFn).isNotNull(); + assertThat(forFn.getDo()).hasSize(2); // scorer + editor inside + assertThat(forFn.getDo().get(0).getTask().getCallTask().get()).isNotNull(); + } +} diff --git a/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentWorkflowBuilderTest.java b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentWorkflowBuilderTest.java new file mode 100644 index 00000000..008fe48b --- /dev/null +++ b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentWorkflowBuilderTest.java @@ -0,0 +1,223 @@ +/* + * 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.fluent.agentic; + +import static io.serverlessworkflow.fluent.agentic.AgentsUtils.newMovieExpert; +import static io.serverlessworkflow.fluent.agentic.Models.BASE_MODEL; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.spy; + +import dev.langchain4j.agentic.AgentServices; +import dev.langchain4j.agentic.cognisphere.Cognisphere; +import io.serverlessworkflow.api.types.ForkTask; +import io.serverlessworkflow.api.types.Task; +import io.serverlessworkflow.api.types.TaskItem; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.api.types.func.CallJava; +import io.serverlessworkflow.api.types.func.ForTaskFunction; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class AgentWorkflowBuilderTest { + + @Test + public void verifyAgentCall() { + Agents.MovieExpert movieExpert = + spy( + AgentServices.agentBuilder(Agents.MovieExpert.class) + .outputName("movies") + .chatModel(BASE_MODEL) + .build()); + + Workflow workflow = + AgentWorkflowBuilder.workflow().tasks(tasks -> tasks.agent("myAgent", movieExpert)).build(); + + assertNotNull(workflow); + assertEquals(1, workflow.getDo().size()); + assertInstanceOf(CallJava.class, workflow.getDo().get(0).getTask().getCallTask().get()); + } + + @Test + void sequenceAgents() { + Agents.MovieExpert movieExpert = newMovieExpert(); + Workflow wf = + AgentWorkflowBuilder.workflow("seqFlow") + .tasks(d -> d.sequence("lineup", movieExpert, movieExpert)) + .build(); + + assertThat(wf.getDo()).hasSize(2); + assertThat(wf.getDo().get(0).getName()).isEqualTo("lineup-0"); + assertThat(wf.getDo().get(1).getName()).isEqualTo("lineup-1"); + wf.getDo() + .forEach( + ti -> { + assertThat(ti.getTask().getCallTask()).isNotNull(); + assertThat(ti.getTask().getCallTask().get()).isNotNull(); + }); + } + + @Test + void mixSpecAndAgent() { + Workflow wf = + AgentWorkflowBuilder.workflow("mixFlow") + .tasks( + d -> + d.set("init", s -> s.expr("$.mood = 'comedy'")) + .agent("pickMovies", newMovieExpert()) + .set("done", "$.done = true")) + .build(); + + assertThat(wf.getDo()).hasSize(3); + assertThat(wf.getDo().get(0).getTask().getSetTask()).isNotNull(); + assertThat(wf.getDo().get(1).getTask().getCallTask().get()).isNotNull(); + assertThat(wf.getDo().get(2).getTask().getSetTask()).isNotNull(); + } + + @Test + void loopOnlyAgents() { + Agents.MovieExpert expert = newMovieExpert(); + + Workflow wf = + AgentWorkflowBuilder.workflow().tasks(d -> d.loop(l -> l.subAgents(expert))).build(); + + assertNotNull(wf); + assertThat(wf.getDo()).hasSize(1); + + TaskItem ti = wf.getDo().get(0); + Task t = ti.getTask(); + assertThat(t.getForTask()).isInstanceOf(ForTaskFunction.class); + + ForTaskFunction fn = (ForTaskFunction) t.getForTask(); + assertNotNull(fn.getDo()); + assertThat(fn.getDo()).hasSize(1); + assertNotNull(fn.getDo().get(0).getTask().getCallTask().get()); + } + + @Test + void loopWithMaxIterationsAndExitCondition() { + Agents.MovieExpert expert = newMovieExpert(); + + AtomicInteger max = new AtomicInteger(4); + Predicate exit = + cog -> { + // stop when we already have at least one movie picked in state + var movies = cog.readState("movies", null); + return movies != null; + }; + + Workflow wf = + AgentWorkflowBuilder.workflow("loop-ctrl") + .tasks( + d -> + d.loop( + "refineMovies", + l -> + l.maxIterations(max.get()) + .exitCondition(exit) + .subAgents("picker", expert))) + .build(); + + TaskItem ti = wf.getDo().get(0); + ForTaskFunction fn = (ForTaskFunction) ti.getTask().getForTask(); + + assertNotNull(fn.getCollection(), "Synthetic collection should exist for maxIterations"); + assertNotNull(fn.getWhilePredicate(), "While predicate set from exitCondition"); + assertThat(fn.getDo()).hasSize(1); + } + + @Test + @DisplayName("parallel() creates one ForkTask with N callFn branches") + void parallelAgents() { + Agents.MovieExpert a1 = AgentsUtils.newMovieExpert(); + Agents.MovieExpert a2 = AgentsUtils.newMovieExpert(); + Agents.MovieExpert a3 = AgentsUtils.newMovieExpert(); + + Workflow wf = + AgentWorkflowBuilder.workflow("parallelFlow") + .tasks(d -> d.parallel("p", a1, a2, a3)) + .build(); + + assertThat(wf.getDo()).hasSize(1); + TaskItem top = wf.getDo().get(0); + Task task = top.getTask(); + assertThat(task.getForkTask()).isInstanceOf(ForkTask.class); + + ForkTask fork = task.getForkTask(); + assertThat(fork.getFork().getBranches()).hasSize(3); + + fork.getFork() + .getBranches() + .forEach( + branch -> { + assertThat(branch.getTask().getCallTask().get()).isInstanceOf(CallJava.class); + }); + } + + @Test + @DisplayName("workflow callFn(name,cfg) produces CallJava with no guard") + void testWorkflowCallFnBare() { + Workflow wf = + AgentWorkflowBuilder.workflow() + .tasks(d -> d.callFn("myCall", fn -> fn.function(ctx -> "hello"))) + .build(); + + assertThat(wf.getDo()).hasSize(1); + TaskItem ti = wf.getDo().get(0); + + assertInstanceOf(CallJava.class, ti.getTask().getCallTask().get()); + } + + @Test + @DisplayName("workflow callFn with Java DSL guard attaches predicate") + void testWorkflowCallFnWithPredicate() { + Predicate guard = cog -> true; + + Workflow wf = + AgentWorkflowBuilder.workflow() + .tasks(d -> d.callFn("guarded", fn -> fn.function(ctx -> "x").when(guard))) + .build(); + + TaskItem ti = wf.getDo().get(0); + assertInstanceOf(CallJava.class, ti.getTask().getCallTask().get()); + } + + @Test + @DisplayName("workflow loop with maxIterations only generates collection and no predicate") + void testWorkflowLoopMaxIterationsOnly() { + Agents.MovieExpert expert = AgentsUtils.newMovieExpert(); + + Workflow wf = + AgentWorkflowBuilder.workflow("maxFlow") + .tasks(d -> d.loop("limit", l -> l.maxIterations(2).subAgents("sub", expert))) + .build(); + + TaskItem ti = wf.getDo().get(0); + ForTaskFunction fn = (ForTaskFunction) ti.getTask().getForTask(); + + // synthetic collection is created + assertThat(fn.getCollection()).isNotNull(); + // no exitCondition → no whilePredicate set + assertNull(fn.getWhilePredicate(), "No while predicate when only maxIterations"); + // inner subAgents block still generates exactly one call branch + assertThat(fn.getDo()).hasSize(1); + } +} diff --git a/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/Agents.java b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/Agents.java new file mode 100644 index 00000000..7215ddd6 --- /dev/null +++ b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/Agents.java @@ -0,0 +1,174 @@ +/* + * 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.fluent.agentic; + +import dev.langchain4j.agentic.Agent; +import dev.langchain4j.agentic.internal.AgentInstance; +import dev.langchain4j.service.UserMessage; +import dev.langchain4j.service.V; +import java.util.List; + +public interface Agents { + + interface MovieExpert { + + @UserMessage( + """ + You are a great evening planner. + Propose a list of 3 movies matching the given mood. + The mood is {mood}. + Provide a list with the 3 items and nothing else. + """) + @Agent + List findMovie(@V("mood") String mood); + } + + interface SettingAgent extends AgentInstance { + + @UserMessage( + """ + Create a vivid {{style}} setting. It should include the time period, the state of technology, + key locations, and a brief description of the world’s political or social situation. + Make it imaginative, atmospheric, and suitable for a {{style}} novel. + """) + @Agent( + "Generates an imaginative setting including timeline, technology level, and world structure") + String invoke(@V("style") String style); + } + + interface HeroAgent extends AgentInstance { + + @UserMessage( + """ + Invent a compelling protagonist for a {{style}} story. Describe their background, personality, + motivations, and any unique skills or traits. + """) + @Agent("Creates a unique and relatable protagonist with rich backstory and motivations.") + String invoke(@V("style") String style); + } + + interface ConflictAgent extends AgentInstance { + + @UserMessage( + """ + Generate a central conflict or threat for a {{style}} plot. It can be external or + internal (e.g. moral dilemma, personal transformation). + Make it high-stakes and thematically rich. + """) + @Agent("Proposes a central conflict or dramatic tension to drive a compelling narrative.") + String invoke(@V("style") String style); + } + + interface FactAgent extends AgentInstance { + + @UserMessage( + """ + Generate a unique sci-fi fact about an alien civilization's {{goal}} environment or evolutionary history. Make it imaginative and specific. + """) + @Agent("Generates a core fact that defines the foundation of an civilization.") + String invoke(@V("fact") String fact); + } + + interface CultureAgent extends AgentInstance { + + @UserMessage( + """ + Given the following sci-fi fact about an civilization, describe 3–5 unique cultural traits, traditions, or societal structures that naturally emerge from this environment. + Fact: + {{fact}} + """) + @Agent("Derives cultural traits from the environmental/evolutionary fact.") + List invoke(@V("fact") String fact); + } + + interface TechnologyAgent extends AgentInstance { + + @UserMessage( + """ + Given the following sci-fi fact about an alien civilization, describe 3–5 technologies or engineering solutions they might have developed. Focus on tools, transportation, communication, and survival systems. + Fact: + {{fact}} + """) + @Agent("Derives plausible technological inventions from the fact.") + List invoke(@V("fact") String fact); + } + + interface StorySeedAgent extends AgentInstance { + + @UserMessage( + """ + You are a science fiction writer. Given the following title, come up with a short story premise. Describe the world, the central concept, and the thematic direction (e.g., dystopia, exploration, AI ethics). + Title: {{title}} + """) + @Agent("Generates a high-level sci-fi premise based on a title.") + String invoke(@V("title") String title); + } + + interface PlotAgent extends AgentInstance { + + @UserMessage( + """ + Using the following premise, outline a three-act structure for a science fiction short story. Include a brief description of the main character, the inciting incident, the rising conflict, and the resolution. + Premise: + {{premise}} + """) + @Agent("Transforms a premise into a structured sci-fi plot.") + String invoke(@V("premise") String premise); + } + + interface SceneAgent extends AgentInstance { + + @UserMessage( + """ + Write the opening scene of a science fiction short story based on the following plot outline. Introduce the main character and immerse the reader in the setting. Use vivid, cinematic language. + Plot: + {{plot}} + """) + @Agent("Generates the opening scene of the story from a plot outline.") + String invoke(@V("plot") String plot); + } + + interface MeetingInvitationDraft extends AgentInstance { + + @UserMessage( + """ + You are a professional meeting invitation writer. Draft a concise and clear meeting invitation email based on the following details: + Subject: {{subject}} + Date: {{date}} + Time: {{time}} + Location: {{location}} + Agenda: {{agenda}} + """) + @Agent("Drafts a professional meeting invitation email.") + String invoke( + @V("subject") String subject, + @V("date") String date, + @V("time") String time, + @V("location") String location, + @V("agenda") String agenda); + } + + interface MeetingInvitationStyle extends AgentInstance { + + @UserMessage( + """ + You are a professional meeting invitation writer. Rewrite the following meeting invitation email to better fit the {{style}} style: + Original Invitation: {{invitation}} + """) + @Agent("Edits a meeting invitation email to better fit a given style.") + String invoke(@V("invitation") String invitation, @V("style") String style); + } +} diff --git a/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentsUtils.java b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentsUtils.java new file mode 100644 index 00000000..a59f62e1 --- /dev/null +++ b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/AgentsUtils.java @@ -0,0 +1,34 @@ +/* + * 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.fluent.agentic; + +import static io.serverlessworkflow.fluent.agentic.Models.BASE_MODEL; +import static org.mockito.Mockito.spy; + +import dev.langchain4j.agentic.AgentServices; + +public final class AgentsUtils { + + private AgentsUtils() {} + + public static Agents.MovieExpert newMovieExpert() { + return spy( + AgentServices.agentBuilder(Agents.MovieExpert.class) + .outputName("movies") + .chatModel(BASE_MODEL) + .build()); + } +} diff --git a/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/Models.java b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/Models.java new file mode 100644 index 00000000..e06aafda --- /dev/null +++ b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/Models.java @@ -0,0 +1,32 @@ +/* + * 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.fluent.agentic; + +import dev.langchain4j.model.chat.ChatModel; +import dev.langchain4j.model.ollama.OllamaChatModel; +import java.time.Duration; + +public class Models { + static final ChatModel BASE_MODEL = + OllamaChatModel.builder() + .baseUrl("http://127.0.0.1:1143") + .modelName("qwen2.5:7b") + .timeout(Duration.ofMinutes(10)) + .temperature(0.0) + .logRequests(true) + .logResponses(true) + .build(); +} diff --git a/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/WorkflowTests.java b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/WorkflowTests.java new file mode 100644 index 00000000..8bbbe62d --- /dev/null +++ b/fluent/agentic/src/test/java/io/serverlessworkflow/fluent/agentic/WorkflowTests.java @@ -0,0 +1,287 @@ +/* + * 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.fluent.agentic; + +import static io.serverlessworkflow.fluent.agentic.Agents.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import dev.langchain4j.agentic.AgentServices; +import dev.langchain4j.agentic.workflow.HumanInTheLoop; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.WorkflowApplication; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +class WorkflowTests { + + @Test + public void testAgent() throws ExecutionException, InterruptedException { + final StorySeedAgent storySeedAgent = mock(StorySeedAgent.class); + + when(storySeedAgent.invoke(eq("A Great Story"))).thenReturn("storySeedAgent"); + when(storySeedAgent.outputName()).thenReturn("premise"); + + Workflow workflow = + AgentWorkflowBuilder.workflow("storyFlow") + .tasks(d -> d.agent("story", storySeedAgent)) + .build(); + + Map topic = new HashMap<>(); + topic.put("title", "A Great Story"); + + try (WorkflowApplication app = WorkflowApplication.builder().build()) { + String result = + app.workflowDefinition(workflow).instance(topic).start().get().asText().orElseThrow(); + + assertEquals("storySeedAgent", result); + } + } + + @Test + public void testAgents() throws ExecutionException, InterruptedException { + final StorySeedAgent storySeedAgent = mock(StorySeedAgent.class); + final PlotAgent plotAgent = mock(PlotAgent.class); + final SceneAgent sceneAgent = mock(SceneAgent.class); + + when(storySeedAgent.invoke(eq("A Great Story"))).thenReturn("storySeedAgent"); + when(storySeedAgent.outputName()).thenReturn("premise"); + + when(plotAgent.invoke(eq("storySeedAgent"))).thenReturn("plotAgent"); + when(plotAgent.outputName()).thenReturn("plot"); + + when(sceneAgent.invoke(eq("plotAgent"))).thenReturn("sceneAgent"); + when(sceneAgent.outputName()).thenReturn("story"); + + Workflow workflow = + AgentWorkflowBuilder.workflow("storyFlow") + .tasks( + d -> + d.agent("story", storySeedAgent) + .agent("plot", plotAgent) + .agent("scene", sceneAgent)) + .build(); + + Map topic = new HashMap<>(); + topic.put("title", "A Great Story"); + + try (WorkflowApplication app = WorkflowApplication.builder().build()) { + String result = + app.workflowDefinition(workflow).instance(topic).start().get().asText().orElseThrow(); + + assertEquals("sceneAgent", result); + } + } + + @Test + public void testSequence() throws ExecutionException, InterruptedException { + final StorySeedAgent storySeedAgent = mock(StorySeedAgent.class); + final PlotAgent plotAgent = mock(PlotAgent.class); + final SceneAgent sceneAgent = mock(SceneAgent.class); + + when(storySeedAgent.invoke(eq("A Great Story"))).thenReturn("storySeedAgent"); + when(storySeedAgent.outputName()).thenReturn("premise"); + + when(plotAgent.invoke(eq("storySeedAgent"))).thenReturn("plotAgent"); + when(plotAgent.outputName()).thenReturn("plot"); + + when(sceneAgent.invoke(eq("plotAgent"))).thenReturn("sceneAgent"); + when(sceneAgent.outputName()).thenReturn("story"); + + Workflow workflow = + AgentWorkflowBuilder.workflow("storyFlow") + .tasks(d -> d.sequence("story", storySeedAgent, plotAgent, sceneAgent)) + .build(); + + Map topic = new HashMap<>(); + topic.put("title", "A Great Story"); + + try (WorkflowApplication app = WorkflowApplication.builder().build()) { + String result = + app.workflowDefinition(workflow).instance(topic).start().get().asText().orElseThrow(); + + assertEquals("sceneAgent", result); + } + } + + @Test + public void testParallel() throws ExecutionException, InterruptedException { + + final SettingAgent setting = mock(SettingAgent.class); + final HeroAgent hero = mock(HeroAgent.class); + final ConflictAgent conflict = mock(ConflictAgent.class); + + when(setting.invoke(eq("sci-fi"))).thenReturn("Fake conflict response"); + when(setting.outputName()).thenReturn("setting"); + + when(hero.invoke(eq("sci-fi"))).thenReturn("Fake hero response"); + when(hero.outputName()).thenReturn("hero"); + + when(conflict.invoke(eq("sci-fi"))).thenReturn("Fake setting response"); + when(conflict.outputName()).thenReturn("conflict"); + + Workflow workflow = + AgentWorkflowBuilder.workflow("parallelFlow") + .tasks(d -> d.parallel("story", setting, hero, conflict)) + .build(); + + Map topic = new HashMap<>(); + topic.put("style", "sci-fi"); + + try (WorkflowApplication app = WorkflowApplication.builder().build()) { + Map result = + app.workflowDefinition(workflow).instance(topic).start().get().asMap().orElseThrow(); + + assertEquals(3, result.size()); + assertTrue(result.containsKey("branch-0-story")); + assertTrue(result.containsKey("branch-1-story")); + assertTrue(result.containsKey("branch-2-story")); + + Set values = + result.values().stream().map(Object::toString).collect(Collectors.toSet()); + + assertTrue(values.contains("Fake conflict response")); + assertTrue(values.contains("Fake hero response")); + assertTrue(values.contains("Fake setting response")); + } + } + + @Test + // TODO: callFn must be replace with a .output() method once it's available + public void testSeqAndThenParallel() throws ExecutionException, InterruptedException { + final FactAgent factAgent = mock(FactAgent.class); + final CultureAgent cultureAgent = mock(CultureAgent.class); + final TechnologyAgent technologyAgent = mock(TechnologyAgent.class); + + List cultureTraits = + List.of("Alien Culture Trait 1", "Alien Culture Trait 2", "Alien Culture Trait 3"); + + List technologyTraits = + List.of("Alien Technology Trait 1", "Alien Technology Trait 2", "Alien Technology Trait 3"); + + when(factAgent.invoke(eq("alien"))).thenReturn("Some Fact about aliens"); + when(factAgent.outputName()).thenReturn("fact"); + + when(cultureAgent.invoke(eq("Some Fact about aliens"))).thenReturn(cultureTraits); + when(cultureAgent.outputName()).thenReturn("culture"); + + when(technologyAgent.invoke(eq("Some Fact about aliens"))).thenReturn(technologyTraits); + when(technologyAgent.outputName()).thenReturn("technology"); + Workflow workflow = + AgentWorkflowBuilder.workflow("alienCultureFlow") + .tasks( + d -> + d.sequence("fact", factAgent) + .callFn( + f -> + f.function( + (Function>) + fact -> { + Map result = new HashMap<>(); + result.put("fact", fact); + return result; + })) + .parallel("cultureAndTechnology", cultureAgent, technologyAgent)) + .build(); + + Map topic = new HashMap<>(); + topic.put("fact", "alien"); + + try (WorkflowApplication app = WorkflowApplication.builder().build()) { + Map result = + app.workflowDefinition(workflow).instance(topic).start().get().asMap().orElseThrow(); + + assertEquals(2, result.size()); + assertTrue(result.containsKey("branch-0-cultureAndTechnology")); + assertTrue(result.containsKey("branch-1-cultureAndTechnology")); + + assertEquals(cultureTraits, result.get("branch-0-cultureAndTechnology")); + assertEquals(technologyTraits, result.get("branch-1-cultureAndTechnology")); + } + } + + @Test + @Disabled("HumanLoop not implemented yet") + public void humanInTheLoop() throws ExecutionException, InterruptedException { + final MeetingInvitationDraft meetingInvitationDraft = mock(MeetingInvitationDraft.class); + when(meetingInvitationDraft.invoke( + eq("Meeting with John Doe"), + eq("2023-10-01"), + eq("08:00AM"), + eq("London"), + eq("Discuss project updates"))) + .thenReturn("Drafted meeting invitation for John Doe"); + when(meetingInvitationDraft.outputName()).thenReturn("draft"); + + final MeetingInvitationStyle meetingInvitationStyle = mock(MeetingInvitationStyle.class); + when(meetingInvitationStyle.invoke(eq("Drafted meeting invitation for John Doe"), eq("formal"))) + .thenReturn("Styled meeting invitation for John Doe"); + when(meetingInvitationStyle.outputName()).thenReturn("styled"); + + AtomicReference request = new AtomicReference<>(); + + HumanInTheLoop humanInTheLoop = + AgentServices.humanInTheLoopBuilder() + .description( + "What level of formality would you like? (please reply with “formal”, “casual”, or “friendly”)") + .inputName("style") + .outputName("style") + .requestWriter( + q -> + request.set( + "What level of formality would you like? (please reply with “formal”, “casual”, or “friendly”)")) + .responseReader(() -> "formal") + .build(); + + Workflow workflow = + AgentWorkflowBuilder.workflow("meetingInvitationFlow") + .tasks( + d -> + d.sequence( + "draft", meetingInvitationDraft, humanInTheLoop, meetingInvitationStyle)) + .build(); + Map initialValues = new HashMap<>(); + initialValues.put("title", "Meeting with John Doe"); + initialValues.put("date", "2023-10-01"); + initialValues.put("time", "08:00AM"); + initialValues.put("location", "London"); + initialValues.put("agenda", "Discuss project updates"); + + try (WorkflowApplication app = WorkflowApplication.builder().build()) { + String result = + app.workflowDefinition(workflow) + .instance(initialValues) + .start() + .get() + .asText() + .orElseThrow(); + + assertEquals("Styled meeting invitation for John Doe", result); + } + } +} diff --git a/fluent/func/pom.xml b/fluent/func/pom.xml new file mode 100644 index 00000000..9f67dcce --- /dev/null +++ b/fluent/func/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-fluent + 8.0.0-SNAPSHOT + + + Serverless Workflow :: Fluent :: Functional + serverlessworkflow-fluent-func + + + 17 + 17 + UTF-8 + + + + + io.serverlessworkflow + serverlessworkflow-experimental-types + + + io.serverlessworkflow + serverlessworkflow-fluent-spec + + + + org.junit.jupiter + junit-jupiter-api + test + + + + \ No newline at end of file diff --git a/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncCallTaskBuilder.java b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncCallTaskBuilder.java new file mode 100644 index 00000000..30d874f0 --- /dev/null +++ b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncCallTaskBuilder.java @@ -0,0 +1,54 @@ +/* + * 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.fluent.func; + +import io.serverlessworkflow.api.types.func.CallJava; +import io.serverlessworkflow.api.types.func.CallTaskJava; +import io.serverlessworkflow.fluent.func.spi.ConditionalTaskBuilder; +import io.serverlessworkflow.fluent.func.spi.FuncTransformations; +import io.serverlessworkflow.fluent.spec.TaskBaseBuilder; +import java.util.function.Function; + +public class FuncCallTaskBuilder extends TaskBaseBuilder + implements FuncTransformations, + ConditionalTaskBuilder { + + private CallTaskJava callTaskJava; + + FuncCallTaskBuilder() { + callTaskJava = new CallTaskJava(new CallJava() {}); + super.setTask(callTaskJava.getCallJava()); + } + + @Override + protected FuncCallTaskBuilder self() { + return this; + } + + public FuncCallTaskBuilder function(Function function) { + return function(function, null); + } + + public FuncCallTaskBuilder function(Function function, Class argClass) { + this.callTaskJava = new CallTaskJava(CallJava.function(function, argClass)); + super.setTask(this.callTaskJava.getCallJava()); + return this; + } + + public CallTaskJava build() { + return this.callTaskJava; + } +} diff --git a/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncDoTaskBuilder.java b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncDoTaskBuilder.java new file mode 100644 index 00000000..723e8d23 --- /dev/null +++ b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncDoTaskBuilder.java @@ -0,0 +1,80 @@ +/* + * 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.fluent.func; + +import io.serverlessworkflow.fluent.func.spi.ConditionalTaskBuilder; +import io.serverlessworkflow.fluent.func.spi.FuncDoFluent; +import io.serverlessworkflow.fluent.func.spi.FuncTransformations; +import io.serverlessworkflow.fluent.spec.BaseDoTaskBuilder; +import java.util.function.Consumer; + +public class FuncDoTaskBuilder extends BaseDoTaskBuilder + implements FuncTransformations, + ConditionalTaskBuilder, + FuncDoFluent { + + public FuncDoTaskBuilder() { + super(new FuncTaskItemListBuilder()); + } + + @Override + public FuncDoTaskBuilder self() { + return this; + } + + @Override + public FuncDoTaskBuilder emit(String name, Consumer itemsConfigurer) { + this.listBuilder().emit(name, itemsConfigurer); + return this; + } + + @Override + public FuncDoTaskBuilder forEach(String name, Consumer itemsConfigurer) { + this.listBuilder().forEach(name, itemsConfigurer); + return this; + } + + @Override + public FuncDoTaskBuilder set(String name, Consumer itemsConfigurer) { + this.listBuilder().set(name, itemsConfigurer); + return this; + } + + @Override + public FuncDoTaskBuilder set(String name, String expr) { + this.listBuilder().set(name, expr); + return this; + } + + @Override + public FuncDoTaskBuilder switchCase( + String name, Consumer itemsConfigurer) { + this.listBuilder().switchCase(name, itemsConfigurer); + return this; + } + + @Override + public FuncDoTaskBuilder callFn(String name, Consumer cfg) { + this.listBuilder().callFn(name, cfg); + return this; + } + + @Override + public FuncDoTaskBuilder fork(String name, Consumer itemsConfigurer) { + this.listBuilder().fork(name, itemsConfigurer); + return this; + } +} diff --git a/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncEmitTaskBuilder.java b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncEmitTaskBuilder.java new file mode 100644 index 00000000..89325ee8 --- /dev/null +++ b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncEmitTaskBuilder.java @@ -0,0 +1,26 @@ +/* + * 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.fluent.func; + +import io.serverlessworkflow.fluent.func.spi.ConditionalTaskBuilder; +import io.serverlessworkflow.fluent.spec.EmitTaskBuilder; + +public class FuncEmitTaskBuilder extends EmitTaskBuilder + implements ConditionalTaskBuilder { + FuncEmitTaskBuilder() { + super(); + } +} diff --git a/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncForTaskBuilder.java b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncForTaskBuilder.java new file mode 100644 index 00000000..9c345178 --- /dev/null +++ b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncForTaskBuilder.java @@ -0,0 +1,124 @@ +/* + * 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.fluent.func; + +import io.serverlessworkflow.api.types.ForTaskConfiguration; +import io.serverlessworkflow.api.types.Task; +import io.serverlessworkflow.api.types.TaskItem; +import io.serverlessworkflow.api.types.func.CallJava; +import io.serverlessworkflow.api.types.func.CallTaskJava; +import io.serverlessworkflow.api.types.func.ForTaskFunction; +import io.serverlessworkflow.fluent.func.spi.ConditionalTaskBuilder; +import io.serverlessworkflow.fluent.func.spi.FuncTransformations; +import io.serverlessworkflow.fluent.spec.TaskBaseBuilder; +import io.serverlessworkflow.fluent.spec.spi.ForEachTaskFluent; +import io.serverlessworkflow.impl.expressions.LoopFunction; +import io.serverlessworkflow.impl.expressions.LoopPredicate; +import io.serverlessworkflow.impl.expressions.LoopPredicateIndex; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.Function; + +public class FuncForTaskBuilder extends TaskBaseBuilder + implements FuncTransformations, + ConditionalTaskBuilder, + ForEachTaskFluent { + + private final ForTaskFunction forTaskFunction; + private final List items; + + FuncForTaskBuilder() { + this.forTaskFunction = new ForTaskFunction(); + this.forTaskFunction.withFor(new ForTaskConfiguration()); + this.items = new ArrayList<>(); + super.setTask(forTaskFunction); + } + + @Override + protected FuncForTaskBuilder self() { + return this; + } + + public FuncForTaskBuilder whileC(LoopPredicate predicate) { + this.forTaskFunction.withWhile(predicate); + return this; + } + + public FuncForTaskBuilder whileC(LoopPredicateIndex predicate) { + this.forTaskFunction.withWhile(predicate); + return this; + } + + public FuncForTaskBuilder collection(Function> collectionF) { + this.forTaskFunction.withCollection(collectionF); + return this; + } + + public FuncForTaskBuilder tasks(String name, LoopFunction function) { + this.items.add( + new TaskItem( + name, + new Task() + .withCallTask( + new CallTaskJava( + CallJava.loopFunction( + function, this.forTaskFunction.getFor().getEach()))))); + return this; + } + + public FuncForTaskBuilder tasks(LoopFunction function) { + return this.tasks(UUID.randomUUID().toString(), function); + } + + @Override + public FuncForTaskBuilder each(String each) { + this.forTaskFunction.getFor().withEach(each); + return this; + } + + @Override + public FuncForTaskBuilder in(String in) { + this.forTaskFunction.getFor().withIn(in); + return this; + } + + @Override + public FuncForTaskBuilder at(String at) { + this.forTaskFunction.getFor().withAt(at); + return this; + } + + @Override + public FuncForTaskBuilder whileC(String expression) { + this.forTaskFunction.setWhile(expression); + return this; + } + + public FuncForTaskBuilder tasks(Consumer consumer) { + final FuncTaskItemListBuilder builder = new FuncTaskItemListBuilder(); + consumer.accept(builder); + this.items.addAll(builder.build()); + return this; + } + + public ForTaskFunction build() { + this.forTaskFunction.setDo(this.items); + return this.forTaskFunction; + } +} diff --git a/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncForkTaskBuilder.java b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncForkTaskBuilder.java new file mode 100644 index 00000000..372da744 --- /dev/null +++ b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncForkTaskBuilder.java @@ -0,0 +1,89 @@ +/* + * 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.fluent.func; + +import io.serverlessworkflow.api.types.ForkTask; +import io.serverlessworkflow.api.types.ForkTaskConfiguration; +import io.serverlessworkflow.api.types.Task; +import io.serverlessworkflow.api.types.TaskItem; +import io.serverlessworkflow.api.types.func.CallJava; +import io.serverlessworkflow.api.types.func.CallTaskJava; +import io.serverlessworkflow.fluent.func.spi.ConditionalTaskBuilder; +import io.serverlessworkflow.fluent.func.spi.FuncTransformations; +import io.serverlessworkflow.fluent.spec.TaskBaseBuilder; +import io.serverlessworkflow.fluent.spec.spi.ForkTaskFluent; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.Function; + +public class FuncForkTaskBuilder extends TaskBaseBuilder + implements FuncTransformations, + ConditionalTaskBuilder, + ForkTaskFluent { + + private final ForkTask forkTask; + private final List items; + + FuncForkTaskBuilder() { + this.forkTask = new ForkTask(); + this.forkTask.setFork(new ForkTaskConfiguration()); + this.items = new ArrayList<>(); + } + + @Override + protected FuncForkTaskBuilder self() { + return this; + } + + public FuncForkTaskBuilder branch(String name, Function function) { + return branch(name, function, null); + } + + public FuncForkTaskBuilder branch( + String name, Function function, Class argParam) { + this.items.add( + new TaskItem( + name, + new Task().withCallTask(new CallTaskJava(CallJava.function(function, argParam))))); + return this; + } + + public FuncForkTaskBuilder branch(Function function) { + return this.branch(UUID.randomUUID().toString(), function); + } + + @Override + public FuncForkTaskBuilder branches(Consumer consumer) { + final FuncTaskItemListBuilder builder = new FuncTaskItemListBuilder(); + consumer.accept(builder); + this.items.addAll(builder.build()); + return this; + } + + @Override + public FuncForkTaskBuilder compete(boolean compete) { + this.forkTask.getFork().setCompete(compete); + return this; + } + + @Override + public ForkTask build() { + this.forkTask.getFork().setBranches(this.items); + return forkTask; + } +} diff --git a/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncSetTaskBuilder.java b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncSetTaskBuilder.java new file mode 100644 index 00000000..fc9753b0 --- /dev/null +++ b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncSetTaskBuilder.java @@ -0,0 +1,25 @@ +/* + * 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.fluent.func; + +import io.serverlessworkflow.fluent.func.spi.ConditionalTaskBuilder; +import io.serverlessworkflow.fluent.spec.SetTaskBuilder; + +public class FuncSetTaskBuilder extends SetTaskBuilder + implements ConditionalTaskBuilder { + + FuncSetTaskBuilder() {} +} diff --git a/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncSwitchTaskBuilder.java b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncSwitchTaskBuilder.java new file mode 100644 index 00000000..d1a1b642 --- /dev/null +++ b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncSwitchTaskBuilder.java @@ -0,0 +1,109 @@ +/* + * 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.fluent.func; + +import io.serverlessworkflow.api.types.FlowDirective; +import io.serverlessworkflow.api.types.FlowDirectiveEnum; +import io.serverlessworkflow.api.types.SwitchCase; +import io.serverlessworkflow.api.types.SwitchItem; +import io.serverlessworkflow.api.types.SwitchTask; +import io.serverlessworkflow.api.types.func.SwitchCaseFunction; +import io.serverlessworkflow.fluent.func.spi.ConditionalTaskBuilder; +import io.serverlessworkflow.fluent.func.spi.FuncTransformations; +import io.serverlessworkflow.fluent.spec.TaskBaseBuilder; +import io.serverlessworkflow.fluent.spec.spi.SwitchTaskFluent; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.Predicate; + +public class FuncSwitchTaskBuilder extends TaskBaseBuilder + implements FuncTransformations, + ConditionalTaskBuilder, + SwitchTaskFluent { + + private final SwitchTask switchTask; + private final List switchItems; + + FuncSwitchTaskBuilder() { + this.switchTask = new SwitchTask(); + this.switchItems = new ArrayList<>(); + super.setTask(switchTask); + } + + @Override + protected FuncSwitchTaskBuilder self() { + return this; + } + + public FuncSwitchTaskBuilder functions(Consumer consumer) { + return this.functions(UUID.randomUUID().toString(), consumer); + } + + public FuncSwitchTaskBuilder functions( + String name, Consumer consumer) { + final SwitchCaseFunctionBuilder switchCase = new SwitchCaseFunctionBuilder(); + consumer.accept(switchCase); + this.switchItems.add(new SwitchItem(name, switchCase.build())); + return this; + } + + @Override + public FuncSwitchTaskBuilder items(String name, Consumer switchCaseConsumer) { + final SwitchCaseBuilder switchCase = new SwitchCaseBuilder(); + switchCaseConsumer.accept(switchCase); + this.switchItems.add(new SwitchItem(name, switchCase.build())); + return this; + } + + public SwitchTask build() { + this.switchTask.setSwitch(this.switchItems); + return switchTask; + } + + public static final class SwitchCaseFunctionBuilder { + private final SwitchCaseFunction switchCase; + + SwitchCaseFunctionBuilder() { + this.switchCase = new SwitchCaseFunction(); + } + + public SwitchCaseFunctionBuilder when(Predicate when) { + this.switchCase.withPredicate(when); + return this; + } + + public SwitchCaseFunctionBuilder when(Predicate when, Class whenClass) { + this.switchCase.withPredicate(when, whenClass); + return this; + } + + public SwitchCaseFunctionBuilder then(FlowDirective then) { + this.switchCase.setThen(then); + return this; + } + + public SwitchCaseFunctionBuilder then(FlowDirectiveEnum then) { + this.switchCase.setThen(new FlowDirective().withFlowDirectiveEnum(then)); + return this; + } + + public SwitchCase build() { + return this.switchCase; + } + } +} diff --git a/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncTaskItemListBuilder.java b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncTaskItemListBuilder.java new file mode 100644 index 00000000..2c8b5524 --- /dev/null +++ b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncTaskItemListBuilder.java @@ -0,0 +1,109 @@ +/* + * 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.fluent.func; + +import io.serverlessworkflow.api.types.Task; +import io.serverlessworkflow.api.types.TaskItem; +import io.serverlessworkflow.fluent.func.spi.FuncDoFluent; +import io.serverlessworkflow.fluent.spec.BaseTaskItemListBuilder; +import java.util.List; +import java.util.UUID; +import java.util.function.Consumer; + +public class FuncTaskItemListBuilder extends BaseTaskItemListBuilder + implements FuncDoFluent { + + public FuncTaskItemListBuilder() { + super(); + } + + public FuncTaskItemListBuilder(final List list) { + super(list); + } + + @Override + protected FuncTaskItemListBuilder self() { + return this; + } + + @Override + protected FuncTaskItemListBuilder newItemListBuilder() { + return new FuncTaskItemListBuilder(); + } + + @Override + public FuncTaskItemListBuilder callFn(String name, Consumer consumer) { + this.requireNameAndConfig(name, consumer); + final FuncCallTaskBuilder callTaskJavaBuilder = new FuncCallTaskBuilder(); + consumer.accept(callTaskJavaBuilder); + return addTaskItem(new TaskItem(name, new Task().withCallTask(callTaskJavaBuilder.build()))); + } + + @Override + public FuncTaskItemListBuilder callFn(Consumer consumer) { + return this.callFn(UUID.randomUUID().toString(), consumer); + } + + @Override + public FuncTaskItemListBuilder set(String name, Consumer itemsConfigurer) { + this.requireNameAndConfig(name, itemsConfigurer); + final FuncSetTaskBuilder funcSetTaskBuilder = new FuncSetTaskBuilder(); + itemsConfigurer.accept(funcSetTaskBuilder); + return this.addTaskItem(new TaskItem(name, new Task().withSetTask(funcSetTaskBuilder.build()))); + } + + @Override + public FuncTaskItemListBuilder set(String name, String expr) { + return this.set(name, s -> s.expr(expr)); + } + + @Override + public FuncTaskItemListBuilder emit(String name, Consumer itemsConfigurer) { + this.requireNameAndConfig(name, itemsConfigurer); + final FuncEmitTaskBuilder emitTaskJavaBuilder = new FuncEmitTaskBuilder(); + itemsConfigurer.accept(emitTaskJavaBuilder); + return this.addTaskItem( + new TaskItem(name, new Task().withEmitTask(emitTaskJavaBuilder.build()))); + } + + @Override + public FuncTaskItemListBuilder forEach( + String name, Consumer itemsConfigurer) { + this.requireNameAndConfig(name, itemsConfigurer); + final FuncForTaskBuilder forTaskJavaBuilder = new FuncForTaskBuilder(); + itemsConfigurer.accept(forTaskJavaBuilder); + return this.addTaskItem(new TaskItem(name, new Task().withForTask(forTaskJavaBuilder.build()))); + } + + @Override + public FuncTaskItemListBuilder switchCase( + String name, Consumer itemsConfigurer) { + this.requireNameAndConfig(name, itemsConfigurer); + final FuncSwitchTaskBuilder funcSwitchTaskBuilder = new FuncSwitchTaskBuilder(); + itemsConfigurer.accept(funcSwitchTaskBuilder); + return this.addTaskItem( + new TaskItem(name, new Task().withSwitchTask(funcSwitchTaskBuilder.build()))); + } + + @Override + public FuncTaskItemListBuilder fork(String name, Consumer itemsConfigurer) { + this.requireNameAndConfig(name, itemsConfigurer); + final FuncForkTaskBuilder forkTaskJavaBuilder = new FuncForkTaskBuilder(); + itemsConfigurer.accept(forkTaskJavaBuilder); + return this.addTaskItem( + new TaskItem(name, new Task().withForkTask(forkTaskJavaBuilder.build()))); + } +} diff --git a/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncWorkflowBuilder.java b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncWorkflowBuilder.java new file mode 100644 index 00000000..261bd88b --- /dev/null +++ b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/FuncWorkflowBuilder.java @@ -0,0 +1,52 @@ +/* + * 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.fluent.func; + +import io.serverlessworkflow.fluent.func.spi.FuncTransformations; +import io.serverlessworkflow.fluent.spec.BaseWorkflowBuilder; +import java.util.UUID; + +public class FuncWorkflowBuilder + extends BaseWorkflowBuilder + implements FuncTransformations { + + protected FuncWorkflowBuilder(final String name, final String namespace, final String version) { + super(name, namespace, version); + } + + public static FuncWorkflowBuilder workflow(final String name, final String namespace) { + return new FuncWorkflowBuilder(name, namespace, DEFAULT_VERSION); + } + + public static FuncWorkflowBuilder workflow(final String name) { + return new FuncWorkflowBuilder(name, DEFAULT_NAMESPACE, DEFAULT_VERSION); + } + + public static FuncWorkflowBuilder workflow() { + return new FuncWorkflowBuilder( + UUID.randomUUID().toString(), DEFAULT_NAMESPACE, DEFAULT_VERSION); + } + + @Override + protected FuncDoTaskBuilder newDo() { + return new FuncDoTaskBuilder(); + } + + @Override + protected FuncWorkflowBuilder self() { + return this; + } +} diff --git a/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/ConditionalTaskBuilder.java b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/ConditionalTaskBuilder.java new file mode 100644 index 00000000..383bf7f3 --- /dev/null +++ b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/ConditionalTaskBuilder.java @@ -0,0 +1,37 @@ +/* + * 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.fluent.func.spi; + +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.api.types.func.TypedPredicate; +import java.util.Objects; +import java.util.function.Predicate; + +public interface ConditionalTaskBuilder { + + TaskBase getTask(); + + default SELF when(Predicate predicate) { + ConditionalTaskBuilderHelper.setMetadata(getTask(), predicate); + return (SELF) this; + } + + default SELF when(Predicate predicate, Class argClass) { + Objects.requireNonNull(argClass); + ConditionalTaskBuilderHelper.setMetadata(getTask(), new TypedPredicate<>(predicate, argClass)); + return (SELF) this; + } +} diff --git a/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/ConditionalTaskBuilderHelper.java b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/ConditionalTaskBuilderHelper.java new file mode 100644 index 00000000..2ffd1d91 --- /dev/null +++ b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/ConditionalTaskBuilderHelper.java @@ -0,0 +1,34 @@ +/* + * 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.fluent.func.spi; + +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.api.types.TaskMetadata; +import io.serverlessworkflow.impl.expressions.TaskMetadataKeys; + +class ConditionalTaskBuilderHelper { + + private ConditionalTaskBuilderHelper() {} + + static void setMetadata(TaskBase task, Object predicate) { + TaskMetadata metadata = task.getMetadata(); + if (metadata == null) { + metadata = new TaskMetadata(); + task.setMetadata(metadata); + } + metadata.setAdditionalProperty(TaskMetadataKeys.IF_PREDICATE, predicate); + } +} diff --git a/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncDoFluent.java b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncDoFluent.java new file mode 100644 index 00000000..434304d5 --- /dev/null +++ b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncDoFluent.java @@ -0,0 +1,46 @@ +/* + * 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.fluent.func.spi; + +import io.serverlessworkflow.fluent.func.FuncCallTaskBuilder; +import io.serverlessworkflow.fluent.func.FuncEmitTaskBuilder; +import io.serverlessworkflow.fluent.func.FuncForTaskBuilder; +import io.serverlessworkflow.fluent.func.FuncForkTaskBuilder; +import io.serverlessworkflow.fluent.func.FuncSetTaskBuilder; +import io.serverlessworkflow.fluent.func.FuncSwitchTaskBuilder; +import io.serverlessworkflow.fluent.spec.spi.EmitFluent; +import io.serverlessworkflow.fluent.spec.spi.ForEachFluent; +import io.serverlessworkflow.fluent.spec.spi.ForkFluent; +import io.serverlessworkflow.fluent.spec.spi.SetFluent; +import io.serverlessworkflow.fluent.spec.spi.SwitchFluent; +import java.util.UUID; +import java.util.function.Consumer; + +// TODO: implement the other builders, e.g. CallHTTP + +public interface FuncDoFluent> + extends SetFluent, + EmitFluent, + ForEachFluent, + SwitchFluent, + ForkFluent { + + SELF callFn(String name, Consumer cfg); + + default SELF callFn(Consumer cfg) { + return this.callFn(UUID.randomUUID().toString(), cfg); + } +} diff --git a/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncTransformations.java b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncTransformations.java new file mode 100644 index 00000000..db257dd0 --- /dev/null +++ b/fluent/func/src/main/java/io/serverlessworkflow/fluent/func/spi/FuncTransformations.java @@ -0,0 +1,59 @@ +/* + * 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.fluent.func.spi; + +import io.serverlessworkflow.api.types.Export; +import io.serverlessworkflow.api.types.Input; +import io.serverlessworkflow.api.types.Output; +import io.serverlessworkflow.api.types.func.ExportAsFunction; +import io.serverlessworkflow.api.types.func.InputFromFunction; +import io.serverlessworkflow.api.types.func.OutputAsFunction; +import io.serverlessworkflow.fluent.spec.spi.TransformationHandlers; +import java.util.function.Function; + +public interface FuncTransformations> + extends TransformationHandlers { + + default SELF exportAsFn(Function function) { + setExport(new Export().withAs(new ExportAsFunction().withFunction(function))); + return (SELF) this; + } + + default SELF exportAsFn(Function function, Class argClass) { + setExport(new Export().withAs(new ExportAsFunction().withFunction(function, argClass))); + return (SELF) this; + } + + default SELF inputFrom(Function function) { + setInput(new Input().withFrom(new InputFromFunction().withFunction(function))); + return (SELF) this; + } + + default SELF inputFrom(Function function, Class argClass) { + setInput(new Input().withFrom(new InputFromFunction().withFunction(function, argClass))); + return (SELF) this; + } + + default SELF outputAs(Function function) { + setOutput(new Output().withAs(new OutputAsFunction().withFunction(function))); + return (SELF) this; + } + + default SELF outputAs(Function function, Class argClass) { + setOutput(new Output().withAs(new OutputAsFunction().withFunction(function, argClass))); + return (SELF) this; + } +} diff --git a/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/JavaWorkflowBuilderTest.java b/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/JavaWorkflowBuilderTest.java new file mode 100644 index 00000000..24de3e7d --- /dev/null +++ b/fluent/func/src/test/java/io/serverlessworkflow/fluent/func/JavaWorkflowBuilderTest.java @@ -0,0 +1,281 @@ +/* + * 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.fluent.func; + +import static org.junit.jupiter.api.Assertions.*; + +import io.serverlessworkflow.api.types.Document; +import io.serverlessworkflow.api.types.Export; +import io.serverlessworkflow.api.types.Output; +import io.serverlessworkflow.api.types.SetTask; +import io.serverlessworkflow.api.types.Task; +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.api.types.TaskItem; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.api.types.func.*; +import io.serverlessworkflow.fluent.spec.BaseWorkflowBuilder; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** Tests for FuncWorkflowBuilder + Java DSL extensions. */ +class JavaWorkflowBuilderTest { + + @Test + @DisplayName("Default Java workflow has auto-generated name and default namespace/version") + void testDefaults() { + Workflow wf = FuncWorkflowBuilder.workflow().build(); + assertNotNull(wf); + Document doc = wf.getDocument(); + assertNotNull(doc); + assertEquals(BaseWorkflowBuilder.DEFAULT_NAMESPACE, doc.getNamespace()); + assertEquals(BaseWorkflowBuilder.DEFAULT_VERSION, doc.getVersion()); + assertEquals(BaseWorkflowBuilder.DSL, doc.getDsl()); + assertNotNull(doc.getName()); + } + + @Test + @DisplayName("Spec style forE still works inside Java workflow") + void testSpecForEachInJavaWorkflow() { + Workflow wf = + FuncWorkflowBuilder.workflow("specLoopFlow") + .tasks( + d -> + d.forEach(f -> f.each("pet").in("$.pets")) + .set("markDone", s -> s.expr("$.done = true"))) + .build(); + + List items = wf.getDo(); + assertEquals(2, items.size()); + + TaskItem loopItem = items.get(0); + assertNotNull(loopItem.getTask().getForTask(), "Spec ForTask should be present"); + + TaskItem setItem = items.get(1); + assertNotNull(setItem.getTask().getSetTask()); + SetTask st = setItem.getTask().getSetTask(); + assertEquals("$.done = true", st.getSet().getString()); + } + + @Test + @DisplayName("Java style forE with collection + whileC builds ForTaskFunction") + void testJavaForEach() { + Workflow wf = + FuncWorkflowBuilder.workflow("javaLoopFlow") + .tasks( + d -> + d.forEach( + j -> + j.collection(ctx -> List.of("a", "b", "c")) + .whileC((String val, Object ctx) -> !val.equals("c")) + .tasks( + inner -> inner.set("loopFlag", s -> s.expr("$.flag = true"))))) + .build(); + + List items = wf.getDo(); + assertEquals(1, items.size()); + + TaskItem loopItem = items.get(0); + Task task = loopItem.getTask(); + + assertNotNull(task.getForTask(), "Java ForTaskFunction should be present"); + + // Basic structural checks on nested do inside the function loop + ForTaskFunction fn = (ForTaskFunction) task.getForTask(); + assertNotNull(fn.getDo(), "Nested 'do' list inside ForTaskFunction should be populated"); + assertEquals(1, fn.getDo().size()); + Task nested = fn.getDo().get(0).getTask(); + assertNotNull(nested.getSetTask()); + } + + @Test + @DisplayName("Mixed spec and Java loops in one workflow") + void testMixedLoops() { + Workflow wf = + FuncWorkflowBuilder.workflow("mixed") + .tasks( + d -> + d.forEach(f -> f.each("item").in("$.array")) // spec + .forEach(j -> j.collection(ctx -> List.of(1, 2, 3))) // java + ) + .build(); + + List items = wf.getDo(); + assertEquals(2, items.size()); + + Task specLoop = items.get(0).getTask(); + Task javaLoop = items.get(1).getTask(); + + assertNotNull(specLoop.getForTask()); + assertNotNull(javaLoop.getForTask()); + } + + @Test + @DisplayName("Java functional exportAsFn/inputFrom/outputAs set function wrappers (not literals)") + void testJavaFunctionalIO() { + AtomicBoolean exportCalled = new AtomicBoolean(false); + AtomicBoolean inputCalled = new AtomicBoolean(false); + AtomicBoolean outputCalled = new AtomicBoolean(false); + + Workflow wf = + FuncWorkflowBuilder.workflow("fnIO") + .tasks( + d -> + d.set("init", s -> s.expr("$.x = 1")) + .forEach( + j -> + j.collection( + ctx -> { + inputCalled.set(true); + return List.of("x", "y"); + }) + .tasks(inner -> inner.set("calc", s -> s.expr("$.y = $.x + 1"))) + .exportAsFn( + item -> { + exportCalled.set(true); + return Map.of("computed", 42); + }) + .outputAs( + item -> { + outputCalled.set(true); + return Map.of("out", true); + }))) + .build(); + + // Top-level 'do' structure + assertEquals(2, wf.getDo().size()); + + // Find nested forTaskFunction + Task forTaskFnHolder = wf.getDo().get(1).getTask(); + ForTaskFunction fn = (ForTaskFunction) forTaskFnHolder.getForTask(); + assertNotNull(fn); + + // Inspect nested branche inside the function loop + List nested = fn.getDo(); + assertEquals(1, nested.size()); + TaskBase nestedTask = nested.get(0).getTask().getSetTask(); + assertNotNull(nestedTask); + + // Because functions are likely stored as opaque objects, we check that + // export / output structures exist and are not expression-based. + Export export = fn.getExport(); + assertNotNull(export, "Export should be set via functional variant"); + assertNull( + export.getAs() != null ? export.getAs().getString() : null, + "Export 'as' should not be a plain string when using function variant"); + + Output out = fn.getOutput(); + // If functional output maps to an OutputAsFunction wrapper, adapt the checks: + if (out != null && out.getAs() != null) { + // Expect no literal string if function used + assertNull(out.getAs().getString(), "Output 'as' should not be a literal string"); + } + + // We can't *invoke* lambdas here (unless your runtime exposes them), + // but we verified structural placement. Flipping AtomicBooleans in creation lambdas + // (collection) at least shows one function executed during build (if it is executed now; + // if they are deferred, remove those assertions.) + } + + @Test + @DisplayName("callFn task added and retains name + CallTask union") + void testCallJavaTask() { + Workflow wf = + FuncWorkflowBuilder.workflow("callJavaFlow") + .tasks( + d -> + d.callFn( + "invokeHandler", + cj -> { + // configure your FuncCallTaskBuilder here + // e.g., cj.className("com.acme.Handler").arg("key", "value"); + })) + .build(); + + List items = wf.getDo(); + assertEquals(1, items.size()); + TaskItem ti = items.get(0); + + assertEquals("invokeHandler", ti.getName()); + Task task = ti.getTask(); + assertNotNull(task.getCallTask(), "CallTask should be present for callFn"); + // Additional assertions if FuncCallTaskBuilder populates fields + // e.g., assertEquals("com.acme.Handler", task.getCallTask().getCallJava().getClassName()); + } + + @Test + @DisplayName("switchCaseFn (Java variant) coexists with spec branche") + void testSwitchCaseJava() { + Workflow wf = + FuncWorkflowBuilder.workflow("switchJava") + .tasks( + d -> + d.set("prepare", s -> s.expr("$.ready = true")) + .switchCase( + sw -> { + // configure Java switch builder (cases / predicates) + })) + .build(); + + List items = wf.getDo(); + assertEquals(2, items.size()); + + Task specSet = items.get(0).getTask(); + Task switchTask = items.get(1).getTask(); + + assertNotNull(specSet.getSetTask()); + assertNotNull(switchTask.getSwitchTask(), "SwitchTask union should be present"); + } + + @Test + @DisplayName("Combined: spec set + java forE + callFn inside nested do") + void testCompositeScenario() { + Workflow wf = + FuncWorkflowBuilder.workflow("composite") + .tasks( + d -> + d.set("init", s -> s.expr("$.val = 0")) + .forEach( + j -> + j.collection(ctx -> List.of("a", "b")) + .tasks( + inner -> + inner + .callFn( + cj -> { + // customizing Java call + }) + .set("flag", s -> s.expr("$.flag = true"))))) + .build(); + + assertEquals(2, wf.getDo().size()); + + Task loopHolder = wf.getDo().get(1).getTask(); + ForTaskFunction fn = (ForTaskFunction) loopHolder.getForTask(); + assertNotNull(fn); + + List nested = fn.getDo(); + assertEquals(2, nested.size()); + + Task nestedCall = nested.get(0).getTask(); + Task nestedSet = nested.get(1).getTask(); + + assertNotNull(nestedCall.getCallTask()); + assertNotNull(nestedSet.getSetTask()); + } +} diff --git a/fluent/pom.xml b/fluent/pom.xml new file mode 100644 index 00000000..51aac994 --- /dev/null +++ b/fluent/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-parent + 8.0.0-SNAPSHOT + + Serverless Workflow :: Fluent + serverlessworkflow-fluent + pom + + + 17 + 17 + UTF-8 + + + + + + io.serverlessworkflow + serverlessworkflow-types + ${project.version} + + + io.serverlessworkflow + serverlessworkflow-experimental-types + ${project.version} + + + io.serverlessworkflow + serverlessworkflow-fluent-spec + ${project.version} + + + io.serverlessworkflow + serverlessworkflow-fluent-func + ${project.version} + + + + + + spec + func + agentic + + + \ No newline at end of file diff --git a/fluent/spec/pom.xml b/fluent/spec/pom.xml new file mode 100644 index 00000000..5ec62a98 --- /dev/null +++ b/fluent/spec/pom.xml @@ -0,0 +1,32 @@ + + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-fluent + 8.0.0-SNAPSHOT + + Serverless Workflow :: Fluent :: Spec + serverlessworkflow-fluent-spec + + + 17 + 17 + UTF-8 + + + + + io.serverlessworkflow + serverlessworkflow-types + + + org.junit.jupiter + junit-jupiter-api + test + + + + \ No newline at end of file diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/AuthenticationPolicyUnionBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/AuthenticationPolicyUnionBuilder.java new file mode 100644 index 00000000..82f84a74 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/AuthenticationPolicyUnionBuilder.java @@ -0,0 +1,80 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.AuthenticationPolicyUnion; +import java.util.function.Consumer; + +public class AuthenticationPolicyUnionBuilder { + final AuthenticationPolicyUnion authenticationPolicy; + + AuthenticationPolicyUnionBuilder() { + this.authenticationPolicy = new AuthenticationPolicyUnion(); + } + + public AuthenticationPolicyUnionBuilder basic( + Consumer basicConsumer) { + final BasicAuthenticationPolicyBuilder basicAuthenticationPolicyBuilder = + new BasicAuthenticationPolicyBuilder(); + basicConsumer.accept(basicAuthenticationPolicyBuilder); + this.authenticationPolicy.setBasicAuthenticationPolicy( + basicAuthenticationPolicyBuilder.build()); + return this; + } + + public AuthenticationPolicyUnionBuilder bearer( + Consumer bearerConsumer) { + final BearerAuthenticationPolicyBuilder bearerAuthenticationPolicyBuilder = + new BearerAuthenticationPolicyBuilder(); + bearerConsumer.accept(bearerAuthenticationPolicyBuilder); + this.authenticationPolicy.setBearerAuthenticationPolicy( + bearerAuthenticationPolicyBuilder.build()); + return this; + } + + public AuthenticationPolicyUnionBuilder digest( + Consumer digestConsumer) { + final DigestAuthenticationPolicyBuilder digestAuthenticationPolicyBuilder = + new DigestAuthenticationPolicyBuilder(); + digestConsumer.accept(digestAuthenticationPolicyBuilder); + this.authenticationPolicy.setDigestAuthenticationPolicy( + digestAuthenticationPolicyBuilder.build()); + return this; + } + + public AuthenticationPolicyUnionBuilder oauth2( + Consumer oauth2Consumer) { + final OAuth2AuthenticationPolicyBuilder oauth2AuthenticationPolicyBuilder = + new OAuth2AuthenticationPolicyBuilder(); + oauth2Consumer.accept(oauth2AuthenticationPolicyBuilder); + this.authenticationPolicy.setOAuth2AuthenticationPolicy( + oauth2AuthenticationPolicyBuilder.build()); + return this; + } + + public AuthenticationPolicyUnionBuilder openIDConnect( + Consumer openIdConnectConsumer) { + final OpenIdConnectAuthenticationPolicyBuilder builder = + new OpenIdConnectAuthenticationPolicyBuilder(); + openIdConnectConsumer.accept(builder); + this.authenticationPolicy.setOpenIdConnectAuthenticationPolicy(builder.build()); + return this; + } + + public AuthenticationPolicyUnion build() { + return authenticationPolicy; + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseDoTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseDoTaskBuilder.java new file mode 100644 index 00000000..476e0e26 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseDoTaskBuilder.java @@ -0,0 +1,46 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.DoTask; + +public abstract class BaseDoTaskBuilder< + SELF extends BaseDoTaskBuilder, LIST extends BaseTaskItemListBuilder> + extends TaskBaseBuilder { + + private final DoTask doTask = new DoTask(); + private final BaseTaskItemListBuilder itemsListBuilder; + + protected BaseDoTaskBuilder(BaseTaskItemListBuilder itemsListBuilder) { + this.itemsListBuilder = itemsListBuilder; + setTask(this.doTask); + } + + protected BaseDoTaskBuilder(BaseTaskItemListBuilder itemsListBuilder, DoTask doTask) { + this.itemsListBuilder = itemsListBuilder; + setTask(doTask); + } + + @SuppressWarnings("unchecked") + protected final LIST listBuilder() { + return (LIST) itemsListBuilder; + } + + public DoTask build() { + doTask.setDo(itemsListBuilder.build()); + return doTask; + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseTaskItemListBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseTaskItemListBuilder.java new file mode 100644 index 00000000..33a424fc --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseTaskItemListBuilder.java @@ -0,0 +1,72 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.api.types.TaskItem; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; + +/** + * A builder for an ordered {@link TaskItem} list. + * + *

This builder only knows how to append new TaskItems of various flavors, but does NOT expose + * {@link TaskBase}‑level methods like export(), input(), etc. Those belong on {@link + * TaskBaseBuilder} subclasses. + * + * @param the concrete builder type + */ +public abstract class BaseTaskItemListBuilder> { + + private final List list; + + public BaseTaskItemListBuilder() { + this.list = new ArrayList<>(); + } + + public BaseTaskItemListBuilder(final List list) { + this.list = list; + } + + protected abstract SELF self(); + + protected abstract SELF newItemListBuilder(); + + protected final List mutableList() { + return this.list; + } + + protected final SELF addTaskItem(TaskItem taskItem) { + Objects.requireNonNull(taskItem, "taskItem must not be null"); + list.add(taskItem); + return self(); + } + + protected final void requireNameAndConfig(String name, Consumer cfg) { + Objects.requireNonNull(name, "Task name must not be null"); + Objects.requireNonNull(cfg, "Configurer must not be null"); + } + + /** + * @return an immutable snapshot of all {@link TaskItem}s added so far + */ + public List build() { + return Collections.unmodifiableList(list); + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseWorkflowBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseWorkflowBuilder.java new file mode 100644 index 00000000..2ab0bb17 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BaseWorkflowBuilder.java @@ -0,0 +1,107 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.Document; +import io.serverlessworkflow.api.types.Export; +import io.serverlessworkflow.api.types.Input; +import io.serverlessworkflow.api.types.Output; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.fluent.spec.spi.TransformationHandlers; +import java.util.function.Consumer; + +public abstract class BaseWorkflowBuilder< + SELF extends BaseWorkflowBuilder, + DBuilder extends BaseDoTaskBuilder, + IListBuilder extends BaseTaskItemListBuilder> + implements TransformationHandlers { + + public static final String DSL = "1.0.0"; + public static final String DEFAULT_VERSION = "0.0.1"; + public static final String DEFAULT_NAMESPACE = "org.acme"; + + private final Workflow workflow; + private final Document document; + + protected BaseWorkflowBuilder(final String name, final String namespace, final String version) { + this.document = new Document(); + this.document.setName(name); + this.document.setNamespace(namespace); + this.document.setVersion(version); + this.document.setDsl(DSL); + this.workflow = new Workflow(); + this.workflow.setDocument(this.document); + } + + protected abstract DBuilder newDo(); + + protected abstract SELF self(); + + @Override + public void setOutput(Output output) { + this.workflow.setOutput(output); + } + + @Override + public void setExport(Export export) { + // TODO: build another interface with only Output and Input + throw new UnsupportedOperationException( + "export() is not supported on the workflow root; only tasks may export"); + } + + @Override + public void setInput(Input input) { + this.workflow.setInput(input); + } + + public SELF document(Consumer documentBuilderConsumer) { + final DocumentBuilder documentBuilder = new DocumentBuilder(this.document); + documentBuilderConsumer.accept(documentBuilder); + return self(); + } + + public SELF use(Consumer useBuilderConsumer) { + final UseBuilder builder = new UseBuilder(); + useBuilderConsumer.accept(builder); + this.workflow.setUse(builder.build()); + return self(); + } + + public SELF tasks(Consumer doTaskConsumer) { + final DBuilder doTaskBuilder = newDo(); + doTaskConsumer.accept(doTaskBuilder); + this.workflow.setDo(doTaskBuilder.build().getDo()); + return self(); + } + + public SELF input(Consumer inputBuilderConsumer) { + final InputBuilder inputBuilder = new InputBuilder(); + inputBuilderConsumer.accept(inputBuilder); + this.workflow.setInput(inputBuilder.build()); + return self(); + } + + public SELF output(Consumer outputBuilderConsumer) { + final OutputBuilder outputBuilder = new OutputBuilder(); + outputBuilderConsumer.accept(outputBuilder); + this.workflow.setOutput(outputBuilder.build()); + return self(); + } + + public Workflow build() { + return this.workflow; + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BasicAuthenticationPolicyBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BasicAuthenticationPolicyBuilder.java new file mode 100644 index 00000000..780a976c --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BasicAuthenticationPolicyBuilder.java @@ -0,0 +1,46 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.BasicAuthenticationPolicy; +import io.serverlessworkflow.api.types.BasicAuthenticationPolicyConfiguration; +import io.serverlessworkflow.api.types.BasicAuthenticationProperties; + +public final class BasicAuthenticationPolicyBuilder { + + private final BasicAuthenticationProperties basicAuthenticationProperties; + + BasicAuthenticationPolicyBuilder() { + this.basicAuthenticationProperties = new BasicAuthenticationProperties(); + } + + public BasicAuthenticationPolicyBuilder username(String username) { + this.basicAuthenticationProperties.setUsername(username); + return this; + } + + public BasicAuthenticationPolicyBuilder password(String password) { + this.basicAuthenticationProperties.setPassword(password); + return this; + } + + public BasicAuthenticationPolicy build() { + final BasicAuthenticationPolicyConfiguration configuration = + new BasicAuthenticationPolicyConfiguration(); + configuration.setBasicAuthenticationProperties(basicAuthenticationProperties); + return new BasicAuthenticationPolicy(configuration); + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BearerAuthenticationPolicyBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BearerAuthenticationPolicyBuilder.java new file mode 100644 index 00000000..04e76b41 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/BearerAuthenticationPolicyBuilder.java @@ -0,0 +1,40 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.BearerAuthenticationPolicy; +import io.serverlessworkflow.api.types.BearerAuthenticationPolicyConfiguration; +import io.serverlessworkflow.api.types.BearerAuthenticationProperties; + +public final class BearerAuthenticationPolicyBuilder { + private final BearerAuthenticationProperties bearerAuthenticationProperties; + + BearerAuthenticationPolicyBuilder() { + this.bearerAuthenticationProperties = new BearerAuthenticationProperties(); + } + + public BearerAuthenticationPolicyBuilder token(final String token) { + this.bearerAuthenticationProperties.setToken(token); + return this; + } + + public BearerAuthenticationPolicy build() { + final BearerAuthenticationPolicyConfiguration configuration = + new BearerAuthenticationPolicyConfiguration(); + configuration.setBearerAuthenticationProperties(bearerAuthenticationProperties); + return new BearerAuthenticationPolicy(configuration); + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/CallHTTPTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/CallHTTPTaskBuilder.java new file mode 100644 index 00000000..95819162 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/CallHTTPTaskBuilder.java @@ -0,0 +1,157 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.CallHTTP; +import io.serverlessworkflow.api.types.Endpoint; +import io.serverlessworkflow.api.types.HTTPArguments; +import io.serverlessworkflow.api.types.HTTPHeaders; +import io.serverlessworkflow.api.types.HTTPQuery; +import io.serverlessworkflow.api.types.Headers; +import io.serverlessworkflow.api.types.Query; +import io.serverlessworkflow.api.types.UriTemplate; +import java.net.URI; +import java.util.Map; +import java.util.function.Consumer; + +public class CallHTTPTaskBuilder extends TaskBaseBuilder { + + private final CallHTTP callHTTP; + + CallHTTPTaskBuilder() { + callHTTP = new CallHTTP(); + callHTTP.setWith(new HTTPArguments()); + callHTTP.getWith().setOutput(HTTPArguments.HTTPOutput.CONTENT); + super.setTask(this.callHTTP); + } + + @Override + protected CallHTTPTaskBuilder self() { + return this; + } + + public CallHTTPTaskBuilder method(String method) { + this.callHTTP.getWith().setMethod(method); + return this; + } + + public CallHTTPTaskBuilder endpoint(URI endpoint) { + this.callHTTP + .getWith() + .setEndpoint(new Endpoint().withUriTemplate(new UriTemplate().withLiteralUri(endpoint))); + return this; + } + + public CallHTTPTaskBuilder endpoint(String expr) { + this.callHTTP.getWith().setEndpoint(new Endpoint().withRuntimeExpression(expr)); + return this; + } + + // TODO: add endpoint configuration to support authentication + + public CallHTTPTaskBuilder headers(String expr) { + this.callHTTP.getWith().setHeaders(new Headers().withRuntimeExpression(expr)); + return this; + } + + public CallHTTPTaskBuilder headers(Consumer consumer) { + HTTPHeadersBuilder hb = new HTTPHeadersBuilder(); + consumer.accept(hb); + callHTTP.getWith().setHeaders(hb.build()); + return this; + } + + public CallHTTPTaskBuilder headers(Map headers) { + HTTPHeadersBuilder hb = new HTTPHeadersBuilder(); + hb.headers(headers); + callHTTP.getWith().setHeaders(hb.build()); + return this; + } + + public CallHTTPTaskBuilder body(Object body) { + this.callHTTP.getWith().setBody(body); + return this; + } + + public CallHTTPTaskBuilder query(String expr) { + this.callHTTP.getWith().setQuery(new Query().withRuntimeExpression(expr)); + return this; + } + + public CallHTTPTaskBuilder query(Consumer consumer) { + HTTPQueryBuilder queryBuilder = new HTTPQueryBuilder(); + consumer.accept(queryBuilder); + callHTTP.getWith().setQuery(queryBuilder.build()); + return this; + } + + public CallHTTPTaskBuilder query(Map query) { + HTTPQueryBuilder httpQueryBuilder = new HTTPQueryBuilder(); + httpQueryBuilder.queries(query); + callHTTP.getWith().setQuery(httpQueryBuilder.build()); + return this; + } + + public CallHTTPTaskBuilder redirect(boolean redirect) { + callHTTP.getWith().setRedirect(redirect); + return this; + } + + public CallHTTPTaskBuilder output(HTTPArguments.HTTPOutput output) { + callHTTP.getWith().setOutput(output); + return this; + } + + public CallHTTP build() { + return callHTTP; + } + + public static class HTTPQueryBuilder { + private final HTTPQuery httpQuery = new HTTPQuery(); + + public HTTPQueryBuilder query(String name, String value) { + httpQuery.setAdditionalProperty(name, value); + return this; + } + + public HTTPQueryBuilder queries(Map headers) { + headers.forEach(httpQuery::setAdditionalProperty); + return this; + } + + public Query build() { + return new Query().withHTTPQuery(httpQuery); + } + } + + public static class HTTPHeadersBuilder { + private final HTTPHeaders httpHeaders = new HTTPHeaders(); + + public HTTPHeadersBuilder header(String name, String value) { + httpHeaders.setAdditionalProperty(name, value); + return this; + } + + public HTTPHeadersBuilder headers(Map headers) { + headers.forEach(httpHeaders::setAdditionalProperty); + return this; + } + + public Headers build() { + return new Headers().withHTTPHeaders(httpHeaders); + } + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DigestAuthenticationPolicyBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DigestAuthenticationPolicyBuilder.java new file mode 100644 index 00000000..a28f6a84 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DigestAuthenticationPolicyBuilder.java @@ -0,0 +1,45 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.DigestAuthenticationPolicy; +import io.serverlessworkflow.api.types.DigestAuthenticationPolicyConfiguration; +import io.serverlessworkflow.api.types.DigestAuthenticationProperties; + +public final class DigestAuthenticationPolicyBuilder { + private final DigestAuthenticationProperties digestAuthenticationProperties; + + DigestAuthenticationPolicyBuilder() { + this.digestAuthenticationProperties = new DigestAuthenticationProperties(); + } + + public DigestAuthenticationPolicyBuilder username(String username) { + this.digestAuthenticationProperties.setUsername(username); + return this; + } + + public DigestAuthenticationPolicyBuilder password(String password) { + this.digestAuthenticationProperties.setPassword(password); + return this; + } + + public DigestAuthenticationPolicy build() { + final DigestAuthenticationPolicyConfiguration configuration = + new DigestAuthenticationPolicyConfiguration(); + configuration.setDigestAuthenticationProperties(digestAuthenticationProperties); + return new DigestAuthenticationPolicy(configuration); + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DoTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DoTaskBuilder.java new file mode 100644 index 00000000..669b580f --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DoTaskBuilder.java @@ -0,0 +1,94 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.fluent.spec.spi.DoFluent; +import java.util.function.Consumer; + +public class DoTaskBuilder extends BaseDoTaskBuilder + implements DoFluent { + + DoTaskBuilder() { + super(new TaskItemListBuilder()); + } + + @Override + protected DoTaskBuilder self() { + return this; + } + + @Override + public DoTaskBuilder callHTTP(String name, Consumer itemsConfigurer) { + this.listBuilder().callHTTP(name, itemsConfigurer); + return this; + } + + @Override + public DoTaskBuilder emit(String name, Consumer itemsConfigurer) { + this.listBuilder().emit(name, itemsConfigurer); + return this; + } + + @Override + public DoTaskBuilder forEach( + String name, Consumer> itemsConfigurer) { + this.listBuilder().forEach(name, itemsConfigurer); + return this; + } + + @Override + public DoTaskBuilder fork(String name, Consumer itemsConfigurer) { + this.listBuilder().fork(name, itemsConfigurer); + return this; + } + + @Override + public DoTaskBuilder listen(String name, Consumer itemsConfigurer) { + this.listBuilder().listen(name, itemsConfigurer); + return this; + } + + @Override + public DoTaskBuilder raise(String name, Consumer itemsConfigurer) { + this.listBuilder().raise(name, itemsConfigurer); + return this; + } + + @Override + public DoTaskBuilder set(String name, Consumer itemsConfigurer) { + this.listBuilder().set(name, itemsConfigurer); + return this; + } + + @Override + public DoTaskBuilder set(String name, String expr) { + this.listBuilder().set(name, expr); + return this; + } + + @Override + public DoTaskBuilder switchCase(String name, Consumer itemsConfigurer) { + this.listBuilder().switchCase(name, itemsConfigurer); + return this; + } + + @Override + public DoTaskBuilder tryCatch( + String name, Consumer> itemsConfigurer) { + this.listBuilder().tryCatch(name, itemsConfigurer); + return this; + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DocumentBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DocumentBuilder.java new file mode 100644 index 00000000..afb13916 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DocumentBuilder.java @@ -0,0 +1,108 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.Document; +import io.serverlessworkflow.api.types.WorkflowMetadata; +import io.serverlessworkflow.api.types.WorkflowTags; +import java.util.function.Consumer; + +public class DocumentBuilder { + + private final Document document; + + DocumentBuilder(final Document document) { + this.document = document; + } + + public DocumentBuilder dsl(final String dsl) { + this.document.setDsl(dsl); + return this; + } + + public DocumentBuilder name(final String name) { + this.document.setName(name); + return this; + } + + public DocumentBuilder namespace(final String namespace) { + this.document.setNamespace(namespace); + return this; + } + + public DocumentBuilder version(final String version) { + this.document.setVersion(version); + return this; + } + + public DocumentBuilder title(final String title) { + this.document.setTitle(title); + return this; + } + + public DocumentBuilder summary(final String summary) { + this.document.setSummary(summary); + return this; + } + + public DocumentBuilder tags(Consumer tagsBuilderConsumer) { + final WorkflowTagsBuilder tagsBuilder = new WorkflowTagsBuilder(); + tagsBuilderConsumer.accept(tagsBuilder); + this.document.setTags(tagsBuilder.build()); + return this; + } + + public DocumentBuilder metadata(Consumer metadataBuilderConsumer) { + final WorkflowMetadataBuilder metadataBuilder = new WorkflowMetadataBuilder(); + metadataBuilderConsumer.accept(metadataBuilder); + this.document.setMetadata(metadataBuilder.build()); + return this; + } + + public static final class WorkflowTagsBuilder { + private final WorkflowTags tags; + + WorkflowTagsBuilder() { + this.tags = new WorkflowTags(); + } + + public WorkflowTagsBuilder tag(final String key, final String value) { + this.tags.withAdditionalProperty(key, value); + return this; + } + + public WorkflowTags build() { + return this.tags; + } + } + + public static final class WorkflowMetadataBuilder { + private final WorkflowMetadata workflowMetadata; + + WorkflowMetadataBuilder() { + this.workflowMetadata = new WorkflowMetadata(); + } + + public WorkflowMetadataBuilder metadata(final String key, final Object value) { + this.workflowMetadata.withAdditionalProperty(key, value); + return this; + } + + public WorkflowMetadata build() { + return this.workflowMetadata; + } + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DurationInlineBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DurationInlineBuilder.java new file mode 100644 index 00000000..f4933bb4 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/DurationInlineBuilder.java @@ -0,0 +1,56 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.DurationInline; + +public class DurationInlineBuilder { + + private final DurationInline duration; + + DurationInlineBuilder() { + duration = new DurationInline(); + } + + public DurationInlineBuilder days(int days) { + duration.setDays(days); + return this; + } + + public DurationInlineBuilder hours(int hours) { + duration.setHours(hours); + return this; + } + + public DurationInlineBuilder minutes(int minutes) { + duration.setMinutes(minutes); + return this; + } + + public DurationInlineBuilder seconds(int seconds) { + duration.setSeconds(seconds); + return this; + } + + public DurationInlineBuilder milliseconds(int milliseconds) { + duration.setMilliseconds(milliseconds); + return this; + } + + public DurationInline build() { + return duration; + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/EmitTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/EmitTaskBuilder.java new file mode 100644 index 00000000..be4aff6e --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/EmitTaskBuilder.java @@ -0,0 +1,49 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.EmitEventDefinition; +import io.serverlessworkflow.api.types.EmitTask; +import io.serverlessworkflow.api.types.EmitTaskConfiguration; +import java.util.function.Consumer; + +public class EmitTaskBuilder extends TaskBaseBuilder { + + private final EmitTask emitTask; + + protected EmitTaskBuilder() { + this.emitTask = new EmitTask(); + super.setTask(emitTask); + } + + public EmitTaskBuilder event(Consumer consumer) { + final EventPropertiesBuilder eventPropertiesBuilder = new EventPropertiesBuilder(); + consumer.accept(eventPropertiesBuilder); + this.emitTask.setEmit( + new EmitTaskConfiguration() + .withEvent(new EmitEventDefinition().withWith(eventPropertiesBuilder.build()))); + return this; + } + + public EmitTask build() { + return emitTask; + } + + @Override + protected EmitTaskBuilder self() { + return this; + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/EventPropertiesBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/EventPropertiesBuilder.java new file mode 100644 index 00000000..ccfccfec --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/EventPropertiesBuilder.java @@ -0,0 +1,78 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.EventData; +import io.serverlessworkflow.api.types.EventProperties; +import io.serverlessworkflow.api.types.EventSource; +import io.serverlessworkflow.api.types.EventTime; +import io.serverlessworkflow.api.types.UriTemplate; +import java.net.URI; +import java.util.Date; + +public final class EventPropertiesBuilder { + private final EventProperties properties = new EventProperties(); + + public EventPropertiesBuilder id(String id) { + properties.setId(id); + return this; + } + + public EventPropertiesBuilder source(String expr) { + + properties.setSource(new EventSource().withRuntimeExpression(expr)); + return this; + } + + public EventPropertiesBuilder source(URI uri) { + properties.setSource(new EventSource().withUriTemplate(new UriTemplate().withLiteralUri(uri))); + return this; + } + + public EventPropertiesBuilder type(String type) { + properties.setType(type); + return this; + } + + public EventPropertiesBuilder time(Date time) { + properties.setTime(new EventTime().withLiteralTime(time)); + return this; + } + + public EventPropertiesBuilder subject(String subject) { + properties.setSubject(subject); + return this; + } + + public EventPropertiesBuilder dataContentType(String ct) { + properties.setDatacontenttype(ct); + return this; + } + + public EventPropertiesBuilder data(String expr) { + properties.setData(new EventData().withRuntimeExpression(expr)); + return this; + } + + public EventPropertiesBuilder data(Object obj) { + properties.setData(new EventData().withObject(obj)); + return this; + } + + public EventProperties build() { + return properties; + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ForEachTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ForEachTaskBuilder.java new file mode 100644 index 00000000..f454e411 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ForEachTaskBuilder.java @@ -0,0 +1,74 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.ForTask; +import io.serverlessworkflow.api.types.ForTaskConfiguration; +import io.serverlessworkflow.fluent.spec.spi.ForEachTaskFluent; +import java.util.function.Consumer; + +public class ForEachTaskBuilder> + extends TaskBaseBuilder> + implements ForEachTaskFluent, T> { + + private final ForTask forTask; + private final ForTaskConfiguration forTaskConfiguration; + private final T taskItemListBuilder; + + public ForEachTaskBuilder(T taskItemListBuilder) { + super(); + forTask = new ForTask(); + forTaskConfiguration = new ForTaskConfiguration(); + this.taskItemListBuilder = taskItemListBuilder; + super.setTask(forTask); + } + + protected ForEachTaskBuilder self() { + return this; + } + + public ForEachTaskBuilder each(String each) { + forTaskConfiguration.setEach(each); + return this; + } + + public ForEachTaskBuilder in(String in) { + this.forTaskConfiguration.setIn(in); + return this; + } + + public ForEachTaskBuilder at(String at) { + this.forTaskConfiguration.setAt(at); + return this; + } + + public ForEachTaskBuilder whileC(final String expression) { + this.forTask.setWhile(expression); + return this; + } + + public ForEachTaskBuilder tasks(Consumer doBuilderConsumer) { + final T taskItemListBuilder = this.taskItemListBuilder.newItemListBuilder(); + doBuilderConsumer.accept(taskItemListBuilder); + this.forTask.setDo(taskItemListBuilder.build()); + return this; + } + + public ForTask build() { + this.forTask.setFor(this.forTaskConfiguration); + return this.forTask; + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ForkTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ForkTaskBuilder.java new file mode 100644 index 00000000..56a21148 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ForkTaskBuilder.java @@ -0,0 +1,58 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.ForkTask; +import io.serverlessworkflow.api.types.ForkTaskConfiguration; +import io.serverlessworkflow.fluent.spec.spi.ForkTaskFluent; +import java.util.function.Consumer; + +public class ForkTaskBuilder extends TaskBaseBuilder + implements ForkTaskFluent { + + private final ForkTask forkTask; + private final ForkTaskConfiguration forkTaskConfiguration; + + ForkTaskBuilder() { + this.forkTask = new ForkTask(); + this.forkTaskConfiguration = new ForkTaskConfiguration(); + super.setTask(this.forkTask); + } + + @Override + protected ForkTaskBuilder self() { + return this; + } + + @Override + public ForkTaskBuilder compete(final boolean compete) { + this.forkTaskConfiguration.setCompete(compete); + return this; + } + + @Override + public ForkTaskBuilder branches(Consumer branchesConsumer) { + final TaskItemListBuilder doTaskBuilder = new TaskItemListBuilder(); + branchesConsumer.accept(doTaskBuilder); + this.forkTaskConfiguration.setBranches(doTaskBuilder.build()); + return this; + } + + @Override + public ForkTask build() { + return this.forkTask.withFork(this.forkTaskConfiguration); + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/InputBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/InputBuilder.java new file mode 100644 index 00000000..1161c82a --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/InputBuilder.java @@ -0,0 +1,67 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.Endpoint; +import io.serverlessworkflow.api.types.ExternalResource; +import io.serverlessworkflow.api.types.Input; +import io.serverlessworkflow.api.types.InputFrom; +import io.serverlessworkflow.api.types.SchemaExternal; +import io.serverlessworkflow.api.types.SchemaInline; +import io.serverlessworkflow.api.types.SchemaUnion; + +public class InputBuilder { + + private final Input input; + + InputBuilder() { + this.input = new Input(); + this.input.setFrom(new InputFrom()); + this.input.setSchema(new SchemaUnion()); + } + + public InputBuilder from(String expr) { + this.input.getFrom().setString(expr); + return this; + } + + public InputBuilder from(Object object) { + this.input.getFrom().setObject(object); + return this; + } + + public InputBuilder schema(Object schema) { + this.input.getSchema().setSchemaInline(new SchemaInline(schema)); + return this; + } + + public InputBuilder schema(String schema) { + this.input + .getSchema() + .setSchemaExternal( + new SchemaExternal() + .withResource( + new ExternalResource() + .withEndpoint( + new Endpoint() + .withUriTemplate(UriTemplateBuilder.newUriTemplate(schema))))); + return this; + } + + public Input build() { + return this.input; + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ListenTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ListenTaskBuilder.java new file mode 100644 index 00000000..5c722cb5 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/ListenTaskBuilder.java @@ -0,0 +1,149 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.AllEventConsumptionStrategy; +import io.serverlessworkflow.api.types.AnyEventConsumptionStrategy; +import io.serverlessworkflow.api.types.CorrelateProperty; +import io.serverlessworkflow.api.types.EventFilter; +import io.serverlessworkflow.api.types.EventFilterCorrelate; +import io.serverlessworkflow.api.types.ListenTask; +import io.serverlessworkflow.api.types.ListenTaskConfiguration; +import io.serverlessworkflow.api.types.ListenTo; +import io.serverlessworkflow.api.types.OneEventConsumptionStrategy; +import java.util.List; +import java.util.function.Consumer; + +/** + * Fluent builder for a "listen" task in a Serverless Workflow. Enforces exactly one consumption + * strategy: one, all, or any. + */ +public class ListenTaskBuilder extends TaskBaseBuilder { + + private final ListenTask listenTask; + private final ListenTaskConfiguration config; + private boolean oneSet, allSet, anySet; + + public ListenTaskBuilder() { + super(); + this.listenTask = new ListenTask(); + this.config = new ListenTaskConfiguration(); + this.config.setTo(new ListenTo()); + this.listenTask.setListen(config); + super.setTask(listenTask); + } + + @Override + protected ListenTaskBuilder self() { + return this; + } + + /** Consume exactly one matching event. */ + public ListenTaskBuilder one(Consumer c) { + ensureNoneSet(); + oneSet = true; + EventFilterBuilder fb = new EventFilterBuilder(); + c.accept(fb); + OneEventConsumptionStrategy strat = new OneEventConsumptionStrategy(); + strat.setOne(fb.build()); + config.getTo().withOneEventConsumptionStrategy(strat); + return this; + } + + /** Consume events only when *all* filters match. */ + public ListenTaskBuilder all(Consumer c) { + ensureNoneSet(); + allSet = true; + EventFilterBuilder fb = new EventFilterBuilder(); + c.accept(fb); + AllEventConsumptionStrategy strat = new AllEventConsumptionStrategy(); + strat.setAll(List.of(fb.build())); + config.getTo().withAllEventConsumptionStrategy(strat); + return this; + } + + /** Consume events when *any* filter matches. */ + public ListenTaskBuilder any(Consumer c) { + ensureNoneSet(); + anySet = true; + EventFilterBuilder fb = new EventFilterBuilder(); + c.accept(fb); + AnyEventConsumptionStrategy strat = new AnyEventConsumptionStrategy(); + strat.setAny(List.of(fb.build())); + config.getTo().withAnyEventConsumptionStrategy(strat); + return this; + } + + private void ensureNoneSet() { + if (oneSet || allSet || anySet) { + throw new IllegalStateException("Only one consumption strategy can be configured"); + } + } + + /** Validate and return the built ListenTask. */ + public ListenTask build() { + if (!(oneSet || allSet || anySet)) { + throw new IllegalStateException( + "A consumption strategy (one, all, or any) must be configured"); + } + return listenTask; + } + + /** Builder for event filters used in consumption strategies. */ + public static final class EventFilterBuilder { + private final EventFilter filter = new EventFilter(); + private final EventFilterCorrelate correlate = new EventFilterCorrelate(); + + /** Predicate to match event properties. */ + public EventFilterBuilder with(Consumer c) { + EventPropertiesBuilder pb = new EventPropertiesBuilder(); + c.accept(pb); + filter.setWith(pb.build()); + return this; + } + + /** Correlation property for the filter. */ + public EventFilterBuilder correlate(String key, Consumer c) { + CorrelatePropertyBuilder cpb = new CorrelatePropertyBuilder(); + c.accept(cpb); + correlate.withAdditionalProperty(key, cpb.build()); + return this; + } + + public EventFilter build() { + filter.setCorrelate(correlate); + return filter; + } + } + + public static final class CorrelatePropertyBuilder { + private final CorrelateProperty prop = new CorrelateProperty(); + + public CorrelatePropertyBuilder from(String expr) { + prop.setFrom(expr); + return this; + } + + public CorrelatePropertyBuilder expect(String val) { + prop.setExpect(val); + return this; + } + + public CorrelateProperty build() { + return prop; + } + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/OAuth2AuthenticationPolicyBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/OAuth2AuthenticationPolicyBuilder.java new file mode 100644 index 00000000..af76983d --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/OAuth2AuthenticationPolicyBuilder.java @@ -0,0 +1,57 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.OAuth2AuthenticationPolicy; +import io.serverlessworkflow.api.types.OAuth2AuthenticationPolicyConfiguration; +import io.serverlessworkflow.api.types.OAuth2ConnectAuthenticationProperties; +import io.serverlessworkflow.api.types.Oauth2; +import java.util.function.Consumer; + +public final class OAuth2AuthenticationPolicyBuilder + extends OIDCBuilder { + + private final OAuth2ConnectAuthenticationProperties properties; + + OAuth2AuthenticationPolicyBuilder() { + super(); + this.properties = new OAuth2ConnectAuthenticationProperties(); + } + + public OAuth2AuthenticationPolicyBuilder endpoints( + Consumer endpointsConsumer) { + final OAuth2AuthenticationPropertiesEndpointsBuilder builder = + new OAuth2AuthenticationPropertiesEndpointsBuilder(); + endpointsConsumer.accept(builder); + this.properties.setEndpoints(builder.build()); + return this; + } + + public OAuth2AuthenticationPolicy build() { + final OAuth2AuthenticationPolicyConfiguration configuration = + new OAuth2AuthenticationPolicyConfiguration(); + configuration.setOAuth2AutenthicationData(this.getAuthenticationData()); + configuration.setOAuth2ConnectAuthenticationProperties(this.properties); + + final Oauth2 oauth2 = new Oauth2(); + oauth2.setOAuth2ConnectAuthenticationProperties(configuration); + + final OAuth2AuthenticationPolicy policy = new OAuth2AuthenticationPolicy(); + policy.setOauth2(oauth2); + + return policy; + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/OIDCBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/OIDCBuilder.java new file mode 100644 index 00000000..f12ffbaf --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/OIDCBuilder.java @@ -0,0 +1,190 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.AuthenticationPolicy; +import io.serverlessworkflow.api.types.OAuth2AutenthicationData; +import io.serverlessworkflow.api.types.OAuth2AutenthicationDataClient; +import io.serverlessworkflow.api.types.OAuth2AuthenticationPropertiesEndpoints; +import io.serverlessworkflow.api.types.OAuth2TokenDefinition; +import io.serverlessworkflow.api.types.OAuth2TokenRequest; +import java.util.List; +import java.util.function.Consumer; + +public abstract class OIDCBuilder { + private final OAuth2AutenthicationData authenticationData; + + OIDCBuilder() { + this.authenticationData = new OAuth2AutenthicationData(); + this.authenticationData.setRequest(new OAuth2TokenRequest()); + } + + public OIDCBuilder authority(String authority) { + this.authenticationData.setAuthority(UriTemplateBuilder.newUriTemplate(authority)); + return this; + } + + public OIDCBuilder grant(OAuth2AutenthicationData.OAuth2AutenthicationDataGrant grant) { + this.authenticationData.setGrant(grant); + return this; + } + + public OIDCBuilder issuers(String... issuers) { + if (issuers != null) { + this.authenticationData.setIssuers(List.of(issuers)); + } + return this; + } + + public OIDCBuilder scopes(String... scopes) { + if (scopes != null) { + this.authenticationData.setScopes(List.of(scopes)); + } + return this; + } + + public OIDCBuilder audiences(String... audiences) { + if (audiences != null) { + this.authenticationData.setAudiences(List.of(audiences)); + } + return this; + } + + public OIDCBuilder username(String username) { + this.authenticationData.setUsername(username); + return this; + } + + public OIDCBuilder password(String password) { + this.authenticationData.setPassword(password); + return this; + } + + public OIDCBuilder requestEncoding(OAuth2TokenRequest.Oauth2TokenRequestEncoding encoding) { + this.authenticationData.setRequest(new OAuth2TokenRequest().withEncoding(encoding)); + return this; + } + + public OIDCBuilder subject(Consumer subjectConsumer) { + final OAuth2TokenDefinitionBuilder builder = new OAuth2TokenDefinitionBuilder(); + subjectConsumer.accept(builder); + this.authenticationData.setSubject(builder.build()); + return this; + } + + public OIDCBuilder actor(Consumer actorConsumer) { + final OAuth2TokenDefinitionBuilder builder = new OAuth2TokenDefinitionBuilder(); + actorConsumer.accept(builder); + this.authenticationData.setActor(builder.build()); + return this; + } + + public OIDCBuilder client(Consumer clientConsumer) { + final OAuth2AuthenticationDataClientBuilder builder = + new OAuth2AuthenticationDataClientBuilder(); + clientConsumer.accept(builder); + this.authenticationData.setClient(builder.build()); + return this; + } + + protected final OAuth2AutenthicationData getAuthenticationData() { + return authenticationData; + } + + public abstract T build(); + + public static final class OAuth2TokenDefinitionBuilder { + private final OAuth2TokenDefinition oauth2TokenDefinition; + + OAuth2TokenDefinitionBuilder() { + this.oauth2TokenDefinition = new OAuth2TokenDefinition(); + } + + public OAuth2TokenDefinitionBuilder token(String token) { + this.oauth2TokenDefinition.setToken(token); + return this; + } + + public OAuth2TokenDefinitionBuilder type(String type) { + this.oauth2TokenDefinition.setType(type); + return this; + } + + public OAuth2TokenDefinition build() { + return this.oauth2TokenDefinition; + } + } + + public static final class OAuth2AuthenticationDataClientBuilder { + private final OAuth2AutenthicationDataClient client; + + OAuth2AuthenticationDataClientBuilder() { + this.client = new OAuth2AutenthicationDataClient(); + } + + public OAuth2AuthenticationDataClientBuilder id(String id) { + this.client.setId(id); + return this; + } + + public OAuth2AuthenticationDataClientBuilder secret(String secret) { + this.client.setSecret(secret); + return this; + } + + public OAuth2AuthenticationDataClientBuilder assertion(String assertion) { + this.client.setAssertion(assertion); + return this; + } + + public OAuth2AuthenticationDataClientBuilder authentication( + OAuth2AutenthicationDataClient.ClientAuthentication authentication) { + this.client.setAuthentication(authentication); + return this; + } + + public OAuth2AutenthicationDataClient build() { + return this.client; + } + } + + public static final class OAuth2AuthenticationPropertiesEndpointsBuilder { + private final OAuth2AuthenticationPropertiesEndpoints endpoints; + + OAuth2AuthenticationPropertiesEndpointsBuilder() { + endpoints = new OAuth2AuthenticationPropertiesEndpoints(); + } + + public OAuth2AuthenticationPropertiesEndpointsBuilder token(String token) { + this.endpoints.setToken(token); + return this; + } + + public OAuth2AuthenticationPropertiesEndpointsBuilder revocation(String revocation) { + this.endpoints.setRevocation(revocation); + return this; + } + + public OAuth2AuthenticationPropertiesEndpointsBuilder introspection(String introspection) { + this.endpoints.setIntrospection(introspection); + return this; + } + + public OAuth2AuthenticationPropertiesEndpoints build() { + return this.endpoints; + } + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/OpenIdConnectAuthenticationPolicyBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/OpenIdConnectAuthenticationPolicyBuilder.java new file mode 100644 index 00000000..b5271006 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/OpenIdConnectAuthenticationPolicyBuilder.java @@ -0,0 +1,36 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.OpenIdConnectAuthenticationPolicy; +import io.serverlessworkflow.api.types.OpenIdConnectAuthenticationPolicyConfiguration; + +public final class OpenIdConnectAuthenticationPolicyBuilder + extends OIDCBuilder { + + OpenIdConnectAuthenticationPolicyBuilder() { + super(); + } + + public OpenIdConnectAuthenticationPolicy build() { + final OpenIdConnectAuthenticationPolicyConfiguration configuration = + new OpenIdConnectAuthenticationPolicyConfiguration(); + configuration.setOpenIdConnectAuthenticationProperties(this.getAuthenticationData()); + final OpenIdConnectAuthenticationPolicy policy = new OpenIdConnectAuthenticationPolicy(); + policy.setOidc(configuration); + return policy; + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/OutputBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/OutputBuilder.java new file mode 100644 index 00000000..58791172 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/OutputBuilder.java @@ -0,0 +1,67 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.Endpoint; +import io.serverlessworkflow.api.types.ExternalResource; +import io.serverlessworkflow.api.types.Output; +import io.serverlessworkflow.api.types.OutputAs; +import io.serverlessworkflow.api.types.SchemaExternal; +import io.serverlessworkflow.api.types.SchemaInline; +import io.serverlessworkflow.api.types.SchemaUnion; + +public class OutputBuilder { + + private final Output output; + + OutputBuilder() { + this.output = new Output(); + this.output.setAs(new OutputAs()); + this.output.setSchema(new SchemaUnion()); + } + + public OutputBuilder as(final String expr) { + this.output.getAs().setString(expr); + return this; + } + + public OutputBuilder as(final Object object) { + this.output.getAs().setObject(object); + return this; + } + + public OutputBuilder schema(final String schema) { + this.output + .getSchema() + .setSchemaExternal( + new SchemaExternal() + .withResource( + new ExternalResource() + .withEndpoint( + new Endpoint() + .withUriTemplate(UriTemplateBuilder.newUriTemplate(schema))))); + return this; + } + + public OutputBuilder schema(final Object schema) { + this.output.getSchema().setSchemaInline(new SchemaInline(schema)); + return this; + } + + public Output build() { + return this.output; + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/RaiseTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/RaiseTaskBuilder.java new file mode 100644 index 00000000..a036cb05 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/RaiseTaskBuilder.java @@ -0,0 +1,101 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.ErrorDetails; +import io.serverlessworkflow.api.types.ErrorTitle; +import io.serverlessworkflow.api.types.ErrorType; +import io.serverlessworkflow.api.types.RaiseTask; +import io.serverlessworkflow.api.types.RaiseTaskConfiguration; +import io.serverlessworkflow.api.types.RaiseTaskError; +import io.serverlessworkflow.api.types.UriTemplate; +import java.net.URI; +import java.util.function.Consumer; + +public class RaiseTaskBuilder extends TaskBaseBuilder { + + private final RaiseTask raiseTask; + + RaiseTaskBuilder() { + this.raiseTask = new RaiseTask(); + setTask(raiseTask); + } + + @Override + protected RaiseTaskBuilder self() { + return this; + } + + public RaiseTaskBuilder error(Consumer consumer) { + final RaiseTaskErrorBuilder raiseTaskErrorBuilder = new RaiseTaskErrorBuilder(); + consumer.accept(raiseTaskErrorBuilder); + this.raiseTask.setRaise(new RaiseTaskConfiguration().withError(raiseTaskErrorBuilder.build())); + return this; + } + + // TODO: validation, one or the other + + public RaiseTaskBuilder error(String errorReference) { + this.raiseTask.setRaise( + new RaiseTaskConfiguration() + .withError(new RaiseTaskError().withRaiseErrorReference(errorReference))); + return this; + } + + public RaiseTask build() { + return this.raiseTask; + } + + public static final class RaiseTaskErrorBuilder { + private final io.serverlessworkflow.api.types.Error error; + + private RaiseTaskErrorBuilder() { + this.error = new io.serverlessworkflow.api.types.Error(); + } + + public RaiseTaskErrorBuilder type(String expression) { + this.error.setType(new ErrorType().withExpressionErrorType(expression)); + return this; + } + + public RaiseTaskErrorBuilder type(URI errorType) { + this.error.setType( + new ErrorType().withLiteralErrorType(new UriTemplate().withLiteralUri(errorType))); + return this; + } + + public RaiseTaskErrorBuilder status(int status) { + this.error.setStatus(status); + return this; + } + + // TODO: change signature to Expression interface since literal and expressions are String + + public RaiseTaskErrorBuilder title(String expression) { + this.error.setTitle(new ErrorTitle().withExpressionErrorTitle(expression)); + return this; + } + + public RaiseTaskErrorBuilder detail(String expression) { + this.error.setDetail(new ErrorDetails().withExpressionErrorDetails(expression)); + return this; + } + + public RaiseTaskError build() { + return new RaiseTaskError().withRaiseErrorDefinition(this.error); + } + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/SetTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/SetTaskBuilder.java new file mode 100644 index 00000000..ced59032 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/SetTaskBuilder.java @@ -0,0 +1,55 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.Set; +import io.serverlessworkflow.api.types.SetTask; +import io.serverlessworkflow.api.types.SetTaskConfiguration; + +public class SetTaskBuilder extends TaskBaseBuilder { + + private final SetTask setTask; + private final SetTaskConfiguration setTaskConfiguration; + + public SetTaskBuilder() { + this.setTask = new SetTask(); + this.setTaskConfiguration = new SetTaskConfiguration(); + this.setTask(setTask); + } + + @Override + protected SetTaskBuilder self() { + return this; + } + + public SetTaskBuilder expr(String expression) { + this.setTask.setSet(new Set().withString(expression)); + return this; + } + + public SetTaskBuilder put(String key, String value) { + setTaskConfiguration.withAdditionalProperty(key, value); + return this; + } + + public SetTask build() { + if (this.setTask.getSet() == null) { + this.setTask.setSet(new Set().withSetTaskConfiguration(setTaskConfiguration)); + } + + return setTask; + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/SwitchTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/SwitchTaskBuilder.java new file mode 100644 index 00000000..8c6cc9a1 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/SwitchTaskBuilder.java @@ -0,0 +1,57 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.SwitchItem; +import io.serverlessworkflow.api.types.SwitchTask; +import io.serverlessworkflow.fluent.spec.spi.SwitchTaskFluent; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public class SwitchTaskBuilder extends TaskBaseBuilder + implements SwitchTaskFluent { + + private final SwitchTask switchTask; + private final List switchItems; + + SwitchTaskBuilder() { + super(); + this.switchTask = new SwitchTask(); + this.switchItems = new ArrayList<>(); + this.setTask(switchTask); + } + + @Override + protected SwitchTaskBuilder self() { + return this; + } + + @Override + public SwitchTaskBuilder items( + final String name, Consumer switchCaseConsumer) { + final SwitchTaskFluent.SwitchCaseBuilder switchCaseBuilder = + new SwitchTaskFluent.SwitchCaseBuilder(); + switchCaseConsumer.accept(switchCaseBuilder); + this.switchItems.add(new SwitchItem(name, switchCaseBuilder.build())); + return this; + } + + public SwitchTask build() { + this.switchTask.setSwitch(this.switchItems); + return this.switchTask; + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TaskBaseBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TaskBaseBuilder.java new file mode 100644 index 00000000..3ce5c203 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TaskBaseBuilder.java @@ -0,0 +1,152 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.Endpoint; +import io.serverlessworkflow.api.types.Export; +import io.serverlessworkflow.api.types.ExportAs; +import io.serverlessworkflow.api.types.ExternalResource; +import io.serverlessworkflow.api.types.FlowDirective; +import io.serverlessworkflow.api.types.FlowDirectiveEnum; +import io.serverlessworkflow.api.types.Input; +import io.serverlessworkflow.api.types.Output; +import io.serverlessworkflow.api.types.SchemaExternal; +import io.serverlessworkflow.api.types.SchemaInline; +import io.serverlessworkflow.api.types.SchemaUnion; +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.fluent.spec.spi.TransformationHandlers; +import java.util.function.Consumer; + +public abstract class TaskBaseBuilder> + implements TransformationHandlers { + private TaskBase task; + + protected TaskBaseBuilder() {} + + protected abstract T self(); + + protected final void setTask(TaskBase task) { + this.task = task; + } + + public final TaskBase getTask() { + return task; + } + + @Override + public void setInput(Input input) { + this.task.setInput(input); + } + + @Override + public void setExport(Export export) { + this.task.setExport(export); + } + + @Override + public void setOutput(Output output) { + this.task.setOutput(output); + } + + /** + * Conditional to execute this task. Parallel to the `if` conditional in the Spec. Replaced by + * `when` since `if` is a reserved word. + * + * @param expression jq expression to evaluate + * @see DSL + * Reference - Task + */ + public T when(String expression) { + this.task.setIf(expression); + return self(); + } + + public T then(FlowDirectiveEnum then) { + this.task.setThen(new FlowDirective().withFlowDirectiveEnum(then)); + return self(); + } + + public T exportAs(Object exportAs) { + this.task.setExport(new ExportBuilder().as(exportAs).build()); + return self(); + } + + public T export(Consumer exportConsumer) { + final ExportBuilder exportBuilder = new ExportBuilder(); + exportConsumer.accept(exportBuilder); + this.task.setExport(exportBuilder.build()); + return self(); + } + + public T input(Consumer inputConsumer) { + final InputBuilder inputBuilder = new InputBuilder(); + inputConsumer.accept(inputBuilder); + this.task.setInput(inputBuilder.build()); + return self(); + } + + public T output(Consumer outputConsumer) { + final OutputBuilder outputBuilder = new OutputBuilder(); + outputConsumer.accept(outputBuilder); + this.task.setOutput(outputBuilder.build()); + return self(); + } + + // TODO: add timeout, metadata + + public static final class ExportBuilder { + private final Export export; + + public ExportBuilder() { + this.export = new Export(); + this.export.setAs(new ExportAs()); + this.export.setSchema(new SchemaUnion()); + } + + public ExportBuilder as(Object as) { + this.export.getAs().withObject(as); + return this; + } + + public ExportBuilder as(String as) { + this.export.getAs().withString(as); + return this; + } + + public ExportBuilder schema(String schema) { + this.export + .getSchema() + .setSchemaExternal( + new SchemaExternal() + .withResource( + new ExternalResource() + .withEndpoint( + new Endpoint() + .withUriTemplate(UriTemplateBuilder.newUriTemplate(schema))))); + return this; + } + + public ExportBuilder schema(Object schema) { + this.export.getSchema().setSchemaInline(new SchemaInline(schema)); + return this; + } + + public Export build() { + return this.export; + } + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TaskItemListBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TaskItemListBuilder.java new file mode 100644 index 00000000..4c82f62a --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TaskItemListBuilder.java @@ -0,0 +1,128 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.CallTask; +import io.serverlessworkflow.api.types.Task; +import io.serverlessworkflow.api.types.TaskItem; +import io.serverlessworkflow.fluent.spec.spi.DoFluent; +import java.util.List; +import java.util.function.Consumer; + +public class TaskItemListBuilder extends BaseTaskItemListBuilder + implements DoFluent { + + public TaskItemListBuilder() { + super(); + } + + public TaskItemListBuilder(List list) { + super(list); + } + + @Override + protected TaskItemListBuilder self() { + return this; + } + + @Override + protected TaskItemListBuilder newItemListBuilder() { + return new TaskItemListBuilder(); + } + + @Override + public TaskItemListBuilder set(String name, Consumer itemsConfigurer) { + requireNameAndConfig(name, itemsConfigurer); + final SetTaskBuilder setBuilder = new SetTaskBuilder(); + itemsConfigurer.accept(setBuilder); + return addTaskItem(new TaskItem(name, new Task().withSetTask(setBuilder.build()))); + } + + @Override + public TaskItemListBuilder set(String name, final String expr) { + return this.set(name, s -> s.expr(expr)); + } + + @Override + public TaskItemListBuilder forEach( + String name, Consumer> itemsConfigurer) { + requireNameAndConfig(name, itemsConfigurer); + final ForEachTaskBuilder forBuilder = + new ForEachTaskBuilder<>(newItemListBuilder()); + itemsConfigurer.accept(forBuilder); + return addTaskItem(new TaskItem(name, new Task().withForTask(forBuilder.build()))); + } + + @Override + public TaskItemListBuilder switchCase(String name, Consumer itemsConfigurer) { + requireNameAndConfig(name, itemsConfigurer); + final SwitchTaskBuilder switchBuilder = new SwitchTaskBuilder(); + itemsConfigurer.accept(switchBuilder); + return addTaskItem(new TaskItem(name, new Task().withSwitchTask(switchBuilder.build()))); + } + + @Override + public TaskItemListBuilder raise(String name, Consumer itemsConfigurer) { + requireNameAndConfig(name, itemsConfigurer); + final RaiseTaskBuilder raiseBuilder = new RaiseTaskBuilder(); + itemsConfigurer.accept(raiseBuilder); + return addTaskItem(new TaskItem(name, new Task().withRaiseTask(raiseBuilder.build()))); + } + + @Override + public TaskItemListBuilder fork(String name, Consumer itemsConfigurer) { + requireNameAndConfig(name, itemsConfigurer); + final ForkTaskBuilder forkBuilder = new ForkTaskBuilder(); + itemsConfigurer.accept(forkBuilder); + return addTaskItem(new TaskItem(name, new Task().withForkTask(forkBuilder.build()))); + } + + @Override + public TaskItemListBuilder listen(String name, Consumer itemsConfigurer) { + requireNameAndConfig(name, itemsConfigurer); + final ListenTaskBuilder listenBuilder = new ListenTaskBuilder(); + itemsConfigurer.accept(listenBuilder); + return addTaskItem(new TaskItem(name, new Task().withListenTask(listenBuilder.build()))); + } + + @Override + public TaskItemListBuilder emit(String name, Consumer itemsConfigurer) { + requireNameAndConfig(name, itemsConfigurer); + final EmitTaskBuilder emitBuilder = new EmitTaskBuilder(); + itemsConfigurer.accept(emitBuilder); + return addTaskItem(new TaskItem(name, new Task().withEmitTask(emitBuilder.build()))); + } + + @Override + public TaskItemListBuilder tryCatch( + String name, Consumer> itemsConfigurer) { + requireNameAndConfig(name, itemsConfigurer); + final TryTaskBuilder tryBuilder = + new TryTaskBuilder<>(this.newItemListBuilder()); + itemsConfigurer.accept(tryBuilder); + return addTaskItem(new TaskItem(name, new Task().withTryTask(tryBuilder.build()))); + } + + @Override + public TaskItemListBuilder callHTTP(String name, Consumer itemsConfigurer) { + requireNameAndConfig(name, itemsConfigurer); + final CallHTTPTaskBuilder callHTTPBuilder = new CallHTTPTaskBuilder(); + itemsConfigurer.accept(callHTTPBuilder); + return addTaskItem( + new TaskItem( + name, new Task().withCallTask(new CallTask().withCallHTTP(callHTTPBuilder.build())))); + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TryTaskBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TryTaskBuilder.java new file mode 100644 index 00000000..a707e916 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/TryTaskBuilder.java @@ -0,0 +1,341 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.CatchErrors; +import io.serverlessworkflow.api.types.Constant; +import io.serverlessworkflow.api.types.ConstantBackoff; +import io.serverlessworkflow.api.types.ErrorFilter; +import io.serverlessworkflow.api.types.Exponential; +import io.serverlessworkflow.api.types.ExponentialBackOff; +import io.serverlessworkflow.api.types.Linear; +import io.serverlessworkflow.api.types.LinearBackoff; +import io.serverlessworkflow.api.types.Retry; +import io.serverlessworkflow.api.types.RetryBackoff; +import io.serverlessworkflow.api.types.RetryLimit; +import io.serverlessworkflow.api.types.RetryLimitAttempt; +import io.serverlessworkflow.api.types.RetryPolicy; +import io.serverlessworkflow.api.types.RetryPolicyJitter; +import io.serverlessworkflow.api.types.TimeoutAfter; +import io.serverlessworkflow.api.types.TryTask; +import io.serverlessworkflow.api.types.TryTaskCatch; +import java.util.function.Consumer; + +public class TryTaskBuilder> + extends TaskBaseBuilder> { + + private final TryTask tryTask; + private final T doTaskBuilderFactory; + + TryTaskBuilder(T doTaskBuilderFactory) { + this.tryTask = new TryTask(); + this.doTaskBuilderFactory = doTaskBuilderFactory; + } + + @Override + protected TryTaskBuilder self() { + return this; + } + + public TryTaskBuilder tryHandler(Consumer consumer) { + final T taskItemListBuilder = this.doTaskBuilderFactory.newItemListBuilder(); + consumer.accept(taskItemListBuilder); + this.tryTask.setTry(taskItemListBuilder.build()); + return this; + } + + public TryTaskBuilder catchHandler(Consumer consumer) { + final TryTaskCatchBuilder catchBuilder = new TryTaskCatchBuilder(); + consumer.accept(catchBuilder); + this.tryTask.setCatch(catchBuilder.build()); + return this; + } + + public TryTask build() { + return tryTask; + } + + public static final class TryTaskCatchBuilder { + private final TryTaskCatch tryTaskCatch; + + TryTaskCatchBuilder() { + this.tryTaskCatch = new TryTaskCatch(); + } + + public TryTaskCatchBuilder as(final String as) { + this.tryTaskCatch.setAs(as); + return this; + } + + public TryTaskCatchBuilder when(final String when) { + this.tryTaskCatch.setWhen(when); + return this; + } + + public TryTaskCatchBuilder exceptWhen(final String exceptWhen) { + this.tryTaskCatch.setExceptWhen(exceptWhen); + return this; + } + + public TryTaskCatchBuilder retry(Consumer consumer) { + final RetryPolicyBuilder retryPolicyBuilder = new RetryPolicyBuilder(); + consumer.accept(retryPolicyBuilder); + this.tryTaskCatch.setRetry(new Retry().withRetryPolicyDefinition(retryPolicyBuilder.build())); + return this; + } + + public TryTaskCatchBuilder errorsWith(Consumer consumer) { + final CatchErrorsBuilder catchErrorsBuilder = new CatchErrorsBuilder(); + consumer.accept(catchErrorsBuilder); + this.tryTaskCatch.setErrors(catchErrorsBuilder.build()); + return this; + } + + public TryTaskCatch build() { + return tryTaskCatch; + } + } + + public static final class CatchErrorsBuilder { + private final ErrorFilter errorFilter; + + CatchErrorsBuilder() { + this.errorFilter = new ErrorFilter(); + } + + public CatchErrorsBuilder type(final String type) { + this.errorFilter.setType(type); + return this; + } + + public CatchErrorsBuilder status(final int status) { + this.errorFilter.setStatus(status); + return this; + } + + public CatchErrorsBuilder instance(final String instance) { + this.errorFilter.setInstance(instance); + return this; + } + + public CatchErrorsBuilder title(final String title) { + this.errorFilter.setTitle(title); + return this; + } + + public CatchErrorsBuilder details(final String details) { + this.errorFilter.setDetails(details); + return this; + } + + public CatchErrors build() { + return new CatchErrors().withWith(this.errorFilter); + } + } + + public static final class RetryPolicyJitterBuilder { + private final RetryPolicyJitter retryPolicyJitter; + + RetryPolicyJitterBuilder() { + this.retryPolicyJitter = new RetryPolicyJitter(); + } + + public RetryPolicyJitter to(Consumer consumer) { + final DurationInlineBuilder durationInlineBuilder = new DurationInlineBuilder(); + consumer.accept(durationInlineBuilder); + this.retryPolicyJitter.setTo( + new TimeoutAfter().withDurationInline(durationInlineBuilder.build())); + return retryPolicyJitter; + } + + public RetryPolicyJitter to(String expression) { + this.retryPolicyJitter.setTo(new TimeoutAfter().withDurationExpression(expression)); + return retryPolicyJitter; + } + + public RetryPolicyJitter from(Consumer consumer) { + final DurationInlineBuilder durationInlineBuilder = new DurationInlineBuilder(); + consumer.accept(durationInlineBuilder); + this.retryPolicyJitter.setFrom( + new TimeoutAfter().withDurationInline(durationInlineBuilder.build())); + return retryPolicyJitter; + } + + public RetryPolicyJitter from(String expression) { + this.retryPolicyJitter.setFrom(new TimeoutAfter().withDurationExpression(expression)); + return retryPolicyJitter; + } + + public RetryPolicyJitter build() { + return retryPolicyJitter; + } + } + + public static final class RetryPolicyBuilder { + private final RetryPolicy retryPolicy; + + RetryPolicyBuilder() { + this.retryPolicy = new RetryPolicy(); + } + + public RetryPolicyBuilder when(final String when) { + this.retryPolicy.setWhen(when); + return this; + } + + public RetryPolicyBuilder exceptWhen(final String exceptWhen) { + this.retryPolicy.setExceptWhen(exceptWhen); + return this; + } + + public RetryPolicyBuilder backoff(Consumer consumer) { + final BackoffBuilder backoffBuilder = new BackoffBuilder(); + consumer.accept(backoffBuilder); + this.retryPolicy.setBackoff(backoffBuilder.build()); + return this; + } + + public RetryPolicyBuilder delay(Consumer consumer) { + final DurationInlineBuilder builder = new DurationInlineBuilder(); + consumer.accept(builder); + this.retryPolicy.setDelay(new TimeoutAfter().withDurationInline(builder.build())); + return this; + } + + public RetryPolicyBuilder delay(String expression) { + this.retryPolicy.setDelay(new TimeoutAfter().withDurationExpression(expression)); + return this; + } + + public RetryPolicyBuilder limit(Consumer consumer) { + final RetryLimitBuilder limitBuilder = new RetryLimitBuilder(); + consumer.accept(limitBuilder); + this.retryPolicy.setLimit(limitBuilder.build()); + return this; + } + + public RetryPolicyBuilder jitter(Consumer consumer) { + final RetryPolicyJitterBuilder jitterBuilder = new RetryPolicyJitterBuilder(); + consumer.accept(jitterBuilder); + this.retryPolicy.setJitter(jitterBuilder.build()); + return this; + } + + public RetryPolicy build() { + return this.retryPolicy; + } + } + + public static final class RetryLimitBuilder { + private final RetryLimit retryLimit; + + RetryLimitBuilder() { + this.retryLimit = new RetryLimit(); + } + + public RetryLimitBuilder duration(Consumer consumer) { + final DurationInlineBuilder builder = new DurationInlineBuilder(); + consumer.accept(builder); + this.retryLimit.setDuration(new TimeoutAfter().withDurationInline(builder.build())); + return this; + } + + public RetryLimitBuilder duration(String expression) { + this.retryLimit.setDuration(new TimeoutAfter().withDurationExpression(expression)); + return this; + } + + public RetryLimitBuilder attempt(Consumer consumer) { + final RetryLimitAttemptBuilder retryLimitAttemptBuilder = new RetryLimitAttemptBuilder(); + consumer.accept(retryLimitAttemptBuilder); + this.retryLimit.setAttempt(retryLimitAttemptBuilder.build()); + return this; + } + + public RetryLimit build() { + return this.retryLimit; + } + } + + public static final class RetryLimitAttemptBuilder { + private final RetryLimitAttempt retryLimitAttempt; + + RetryLimitAttemptBuilder() { + this.retryLimitAttempt = new RetryLimitAttempt(); + } + + public RetryLimitAttemptBuilder count(int count) { + this.retryLimitAttempt.setCount(count); + return this; + } + + public RetryLimitAttemptBuilder duration(Consumer consumer) { + final DurationInlineBuilder builder = new DurationInlineBuilder(); + consumer.accept(builder); + this.retryLimitAttempt.setDuration(new TimeoutAfter().withDurationInline(builder.build())); + return this; + } + + public RetryLimitAttemptBuilder duration(String expression) { + this.retryLimitAttempt.setDuration(new TimeoutAfter().withDurationExpression(expression)); + return this; + } + + public RetryLimitAttempt build() { + return this.retryLimitAttempt; + } + } + + public static final class BackoffBuilder { + private final RetryBackoff retryBackoff; + private final ConstantBackoff constantBackoff; + private final ExponentialBackOff exponentialBackOff; + private final LinearBackoff linearBackoff; + + BackoffBuilder() { + this.retryBackoff = new RetryBackoff(); + + this.constantBackoff = new ConstantBackoff(); + this.constantBackoff.setConstant(new Constant()); + this.exponentialBackOff = new ExponentialBackOff(); + this.exponentialBackOff.setExponential(new Exponential()); + this.linearBackoff = new LinearBackoff(); + this.linearBackoff.setLinear(new Linear()); + } + + public BackoffBuilder constant(String key, String value) { + this.constantBackoff.getConstant().withAdditionalProperty(key, value); + return this; + } + + public BackoffBuilder exponential(String key, String value) { + this.exponentialBackOff.getExponential().withAdditionalProperty(key, value); + return this; + } + + public BackoffBuilder linear(String key, String value) { + this.linearBackoff.getLinear().withAdditionalProperty(key, value); + return this; + } + + public RetryBackoff build() { + this.retryBackoff.setConstantBackoff(constantBackoff); + this.retryBackoff.setExponentialBackOff(exponentialBackOff); + this.retryBackoff.setLinearBackoff(linearBackoff); + return this.retryBackoff; + } + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/UriTemplateBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/UriTemplateBuilder.java new file mode 100644 index 00000000..5fb74102 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/UriTemplateBuilder.java @@ -0,0 +1,31 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.UriTemplate; +import java.net.URI; +import java.net.URISyntaxException; + +public final class UriTemplateBuilder { + + public static UriTemplate newUriTemplate(String uri) { + try { + return new UriTemplate().withLiteralUri(new URI(uri)); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/UseAuthenticationsBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/UseAuthenticationsBuilder.java new file mode 100644 index 00000000..d865c802 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/UseAuthenticationsBuilder.java @@ -0,0 +1,40 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.UseAuthentications; +import java.util.function.Consumer; + +public class UseAuthenticationsBuilder { + + private final UseAuthentications authentication; + + UseAuthenticationsBuilder() { + this.authentication = new UseAuthentications(); + } + + public UseAuthenticationsBuilder authentication( + String name, Consumer authenticationConsumer) { + final AuthenticationPolicyUnionBuilder builder = new AuthenticationPolicyUnionBuilder(); + authenticationConsumer.accept(builder); + this.authentication.setAdditionalProperty(name, builder.build()); + return this; + } + + public UseAuthentications build() { + return authentication; + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/UseBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/UseBuilder.java new file mode 100644 index 00000000..66833111 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/UseBuilder.java @@ -0,0 +1,49 @@ +/* + * 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.fluent.spec; + +import io.serverlessworkflow.api.types.Use; +import java.util.List; +import java.util.function.Consumer; + +public class UseBuilder { + + private final Use use; + + UseBuilder() { + this.use = new Use(); + } + + public UseBuilder secrets(final String... secrets) { + if (secrets != null) { + this.use.setSecrets(List.of(secrets)); + } + return this; + } + + public UseBuilder authentications(Consumer authenticationsConsumer) { + final UseAuthenticationsBuilder builder = new UseAuthenticationsBuilder(); + authenticationsConsumer.accept(builder); + this.use.setAuthentications(builder.build()); + return this; + } + + // TODO: implement the remaining `use` attributes + + public Use build() { + return use; + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/WorkflowBuilder.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/WorkflowBuilder.java new file mode 100644 index 00000000..374a519f --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/WorkflowBuilder.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.fluent.spec; + +import java.util.UUID; + +public class WorkflowBuilder + extends BaseWorkflowBuilder { + + private WorkflowBuilder(final String name, final String namespace, final String version) { + super(name, namespace, version); + } + + @Override + protected DoTaskBuilder newDo() { + return new DoTaskBuilder(); + } + + public static WorkflowBuilder workflow( + final String name, final String namespace, final String version) { + return new WorkflowBuilder(name, namespace, version); + } + + public static WorkflowBuilder workflow(final String name, final String namespace) { + return new WorkflowBuilder(name, namespace, DEFAULT_VERSION); + } + + public static WorkflowBuilder workflow(final String name) { + return new WorkflowBuilder(name, DEFAULT_NAMESPACE, DEFAULT_VERSION); + } + + public static WorkflowBuilder workflow() { + return new WorkflowBuilder(UUID.randomUUID().toString(), DEFAULT_NAMESPACE, DEFAULT_VERSION); + } + + @Override + protected WorkflowBuilder self() { + return this; + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderConsumers.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderConsumers.java new file mode 100644 index 00000000..e0dbb54c --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderConsumers.java @@ -0,0 +1,37 @@ +/* + * 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.fluent.spec; + +import java.util.function.Consumer; + +public final class WorkflowBuilderConsumers { + + private WorkflowBuilderConsumers() {} + + public static Consumer authBasic( + final String username, final String password) { + return auth -> auth.basic(b -> b.username(username).password(password)); + } + + public static Consumer authBearer(final String token) { + return auth -> auth.bearer(b -> b.token(token)); + } + + public static Consumer authDigest( + final String username, final String password) { + return auth -> auth.digest(d -> d.username(username).password(password)); + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallHTTPFluent.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallHTTPFluent.java new file mode 100644 index 00000000..48547057 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/CallHTTPFluent.java @@ -0,0 +1,29 @@ +/* + * 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.fluent.spec.spi; + +import io.serverlessworkflow.fluent.spec.TaskBaseBuilder; +import java.util.UUID; +import java.util.function.Consumer; + +public interface CallHTTPFluent, LIST> { + + LIST callHTTP(String name, Consumer itemsConfigurer); + + default LIST callHTTP(Consumer itemsConfigurer) { + return this.callHTTP(UUID.randomUUID().toString(), itemsConfigurer); + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/DoFluent.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/DoFluent.java new file mode 100644 index 00000000..a18a08bf --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/DoFluent.java @@ -0,0 +1,45 @@ +/* + * 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.fluent.spec.spi; + +import io.serverlessworkflow.fluent.spec.CallHTTPTaskBuilder; +import io.serverlessworkflow.fluent.spec.EmitTaskBuilder; +import io.serverlessworkflow.fluent.spec.ForEachTaskBuilder; +import io.serverlessworkflow.fluent.spec.ForkTaskBuilder; +import io.serverlessworkflow.fluent.spec.ListenTaskBuilder; +import io.serverlessworkflow.fluent.spec.RaiseTaskBuilder; +import io.serverlessworkflow.fluent.spec.SetTaskBuilder; +import io.serverlessworkflow.fluent.spec.SwitchTaskBuilder; +import io.serverlessworkflow.fluent.spec.TaskItemListBuilder; +import io.serverlessworkflow.fluent.spec.TryTaskBuilder; + +/** + * Documents the exposed fluent `do` DSL. + * + * @see CNCF + * DSL Reference - Do + */ +public interface DoFluent + extends SetFluent, + SwitchFluent, + TryCatchFluent, T>, + CallHTTPFluent, + EmitFluent, + ForEachFluent, T>, + ForkFluent, + ListenFluent, + RaiseFluent {} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/EmitFluent.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/EmitFluent.java new file mode 100644 index 00000000..06ab8d12 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/EmitFluent.java @@ -0,0 +1,29 @@ +/* + * 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.fluent.spec.spi; + +import io.serverlessworkflow.fluent.spec.TaskBaseBuilder; +import java.util.UUID; +import java.util.function.Consumer; + +public interface EmitFluent, LIST> { + + LIST emit(String name, Consumer itemsConfigurer); + + default LIST emit(Consumer itemsConfigurer) { + return emit(UUID.randomUUID().toString(), itemsConfigurer); + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/ForEachFluent.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/ForEachFluent.java new file mode 100644 index 00000000..53a35e57 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/ForEachFluent.java @@ -0,0 +1,29 @@ +/* + * 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.fluent.spec.spi; + +import io.serverlessworkflow.fluent.spec.TaskBaseBuilder; +import java.util.UUID; +import java.util.function.Consumer; + +public interface ForEachFluent, LIST> { + + LIST forEach(String name, Consumer itemsConfigurer); + + default LIST forEach(Consumer itemsConfigurer) { + return this.forEach(UUID.randomUUID().toString(), itemsConfigurer); + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/ForEachTaskFluent.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/ForEachTaskFluent.java new file mode 100644 index 00000000..4ca6d323 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/ForEachTaskFluent.java @@ -0,0 +1,37 @@ +/* + * 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.fluent.spec.spi; + +import io.serverlessworkflow.api.types.ForTask; +import io.serverlessworkflow.fluent.spec.BaseTaskItemListBuilder; +import io.serverlessworkflow.fluent.spec.TaskBaseBuilder; +import java.util.function.Consumer; + +public interface ForEachTaskFluent< + SELF extends TaskBaseBuilder, L extends BaseTaskItemListBuilder> { + + SELF each(String each); + + SELF in(String in); + + SELF at(String at); + + SELF whileC(final String expression); + + SELF tasks(Consumer doBuilderConsumer); + + ForTask build(); +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/ForkFluent.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/ForkFluent.java new file mode 100644 index 00000000..708c41f8 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/ForkFluent.java @@ -0,0 +1,29 @@ +/* + * 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.fluent.spec.spi; + +import io.serverlessworkflow.fluent.spec.TaskBaseBuilder; +import java.util.UUID; +import java.util.function.Consumer; + +public interface ForkFluent, LIST> { + + LIST fork(String name, Consumer itemsConfigurer); + + default LIST fork(Consumer itemsConfigurer) { + return this.fork(UUID.randomUUID().toString(), itemsConfigurer); + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/ForkTaskFluent.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/ForkTaskFluent.java new file mode 100644 index 00000000..31c40c9b --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/ForkTaskFluent.java @@ -0,0 +1,31 @@ +/* + * 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.fluent.spec.spi; + +import io.serverlessworkflow.api.types.ForkTask; +import io.serverlessworkflow.fluent.spec.BaseTaskItemListBuilder; +import io.serverlessworkflow.fluent.spec.TaskBaseBuilder; +import java.util.function.Consumer; + +public interface ForkTaskFluent< + SELF extends TaskBaseBuilder, L extends BaseTaskItemListBuilder> { + + SELF compete(final boolean compete); + + SELF branches(Consumer branchesConsumer); + + ForkTask build(); +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/ListenFluent.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/ListenFluent.java new file mode 100644 index 00000000..c3d32e14 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/ListenFluent.java @@ -0,0 +1,29 @@ +/* + * 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.fluent.spec.spi; + +import io.serverlessworkflow.fluent.spec.TaskBaseBuilder; +import java.util.UUID; +import java.util.function.Consumer; + +public interface ListenFluent, LIST> { + + LIST listen(String name, Consumer itemsConfigurer); + + default LIST listen(Consumer itemsConfigurer) { + return this.listen(UUID.randomUUID().toString(), itemsConfigurer); + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/RaiseFluent.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/RaiseFluent.java new file mode 100644 index 00000000..699162d9 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/RaiseFluent.java @@ -0,0 +1,29 @@ +/* + * 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.fluent.spec.spi; + +import io.serverlessworkflow.fluent.spec.TaskBaseBuilder; +import java.util.UUID; +import java.util.function.Consumer; + +public interface RaiseFluent, LIST> { + + LIST raise(String name, Consumer itemsConfigurer); + + default LIST raise(Consumer itemsConfigurer) { + return this.raise(UUID.randomUUID().toString(), itemsConfigurer); + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/SetFluent.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/SetFluent.java new file mode 100644 index 00000000..3bd743e7 --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/SetFluent.java @@ -0,0 +1,35 @@ +/* + * 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.fluent.spec.spi; + +import io.serverlessworkflow.fluent.spec.TaskBaseBuilder; +import java.util.UUID; +import java.util.function.Consumer; + +public interface SetFluent, LIST> { + + LIST set(String name, Consumer itemsConfigurer); + + LIST set(String name, final String expr); + + default LIST set(final String expr) { + return this.set(UUID.randomUUID().toString(), expr); + } + + default LIST set(Consumer itemsConfigurer) { + return this.set(UUID.randomUUID().toString(), itemsConfigurer); + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/SwitchFluent.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/SwitchFluent.java new file mode 100644 index 00000000..affba92e --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/SwitchFluent.java @@ -0,0 +1,29 @@ +/* + * 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.fluent.spec.spi; + +import io.serverlessworkflow.fluent.spec.TaskBaseBuilder; +import java.util.UUID; +import java.util.function.Consumer; + +public interface SwitchFluent, LIST> { + + LIST switchCase(String name, Consumer itemsConfigurer); + + default LIST switchCase(Consumer itemsConfigurer) { + return this.switchCase(UUID.randomUUID().toString(), itemsConfigurer); + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/SwitchTaskFluent.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/SwitchTaskFluent.java new file mode 100644 index 00000000..1543660b --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/SwitchTaskFluent.java @@ -0,0 +1,61 @@ +/* + * 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.fluent.spec.spi; + +import io.serverlessworkflow.api.types.FlowDirective; +import io.serverlessworkflow.api.types.FlowDirectiveEnum; +import io.serverlessworkflow.api.types.SwitchCase; +import io.serverlessworkflow.api.types.SwitchTask; +import io.serverlessworkflow.fluent.spec.TaskBaseBuilder; +import java.util.UUID; +import java.util.function.Consumer; + +public interface SwitchTaskFluent> { + default SELF items(Consumer switchCaseConsumer) { + return this.items(UUID.randomUUID().toString(), switchCaseConsumer); + } + + SELF items(final String name, Consumer switchCaseConsumer); + + SwitchTask build(); + + final class SwitchCaseBuilder { + private final SwitchCase switchCase; + + public SwitchCaseBuilder() { + this.switchCase = new SwitchCase(); + } + + public SwitchCaseBuilder when(String when) { + this.switchCase.setWhen(when); + return this; + } + + public SwitchCaseBuilder then(FlowDirective then) { + this.switchCase.setThen(then); + return this; + } + + public SwitchCaseBuilder then(FlowDirectiveEnum then) { + this.switchCase.setThen(new FlowDirective().withFlowDirectiveEnum(then)); + return this; + } + + public SwitchCase build() { + return this.switchCase; + } + } +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/TransformationHandlers.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/TransformationHandlers.java new file mode 100644 index 00000000..5894109f --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/TransformationHandlers.java @@ -0,0 +1,29 @@ +/* + * 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.fluent.spec.spi; + +import io.serverlessworkflow.api.types.Export; +import io.serverlessworkflow.api.types.Input; +import io.serverlessworkflow.api.types.Output; + +public interface TransformationHandlers { + + void setOutput(final Output output); + + void setExport(final Export export); + + void setInput(final Input input); +} diff --git a/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/TryCatchFluent.java b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/TryCatchFluent.java new file mode 100644 index 00000000..ea2f82af --- /dev/null +++ b/fluent/spec/src/main/java/io/serverlessworkflow/fluent/spec/spi/TryCatchFluent.java @@ -0,0 +1,28 @@ +/* + * 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.fluent.spec.spi; + +import io.serverlessworkflow.fluent.spec.TaskBaseBuilder; +import java.util.UUID; +import java.util.function.Consumer; + +public interface TryCatchFluent, LIST> { + LIST tryCatch(String name, Consumer itemsConfigurer); + + default LIST tryCatch(Consumer itemsConfigurer) { + return this.tryCatch(UUID.randomUUID().toString(), itemsConfigurer); + } +} diff --git a/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderTest.java b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderTest.java new file mode 100644 index 00000000..dbb9f1d3 --- /dev/null +++ b/fluent/spec/src/test/java/io/serverlessworkflow/fluent/spec/WorkflowBuilderTest.java @@ -0,0 +1,518 @@ +/* + * 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.fluent.spec; + +import static io.serverlessworkflow.fluent.spec.WorkflowBuilderConsumers.authBasic; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.serverlessworkflow.api.types.AuthenticationPolicyUnion; +import io.serverlessworkflow.api.types.CallHTTP; +import io.serverlessworkflow.api.types.CatchErrors; +import io.serverlessworkflow.api.types.Document; +import io.serverlessworkflow.api.types.ErrorFilter; +import io.serverlessworkflow.api.types.EventFilter; +import io.serverlessworkflow.api.types.HTTPArguments; +import io.serverlessworkflow.api.types.HTTPHeaders; +import io.serverlessworkflow.api.types.HTTPQuery; +import io.serverlessworkflow.api.types.ListenTask; +import io.serverlessworkflow.api.types.OneEventConsumptionStrategy; +import io.serverlessworkflow.api.types.RetryLimitAttempt; +import io.serverlessworkflow.api.types.RetryPolicy; +import io.serverlessworkflow.api.types.SetTask; +import io.serverlessworkflow.api.types.TaskItem; +import io.serverlessworkflow.api.types.TryTask; +import io.serverlessworkflow.api.types.TryTaskCatch; +import io.serverlessworkflow.api.types.Use; +import io.serverlessworkflow.api.types.UseAuthentications; +import io.serverlessworkflow.api.types.Workflow; +import java.net.URI; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +/** Unit tests for the fluent WorkflowBuilder API (using static consumers). */ +public class WorkflowBuilderTest { + + @Test + void testWorkflowDocumentDefaults() { + // Use default name, namespace, version + Workflow wf = WorkflowBuilder.workflow().build(); + assertNotNull(wf, "Workflow should not be null"); + Document doc = wf.getDocument(); + assertNotNull(doc, "Document should not be null"); + assertEquals("org.acme", doc.getNamespace(), "Default namespace should be org.acme"); + assertEquals("0.0.1", doc.getVersion(), "Default version should be 0.0.1"); + assertEquals("1.0.0", doc.getDsl(), "DSL version should be set to 1.0.0"); + assertNotNull(doc.getName(), "Name should be auto-generated"); + } + + @Test + void testWorkflowDocumentExplicit() { + Workflow wf = + WorkflowBuilder.workflow("myFlow", "myNs", "1.2.3") + .document(d -> d.dsl("1.0.0").namespace("myNs").name("myFlow").version("1.2.3")) + .build(); + + Document doc = wf.getDocument(); + assertEquals("1.0.0", doc.getDsl()); + assertEquals("myNs", doc.getNamespace()); + assertEquals("myFlow", doc.getName()); + assertEquals("1.2.3", doc.getVersion()); + } + + @Test + void testUseAuthenticationsBasic() { + Workflow wf = + WorkflowBuilder.workflow("flowAuth") + .use( + u -> + u.authentications( + a -> a.authentication("basicAuth", authBasic("admin", "pass")))) + .build(); + + Use use = wf.getUse(); + assertNotNull(use, "Use must not be null"); + UseAuthentications auths = use.getAuthentications(); + assertNotNull(auths, "Authentications map must not be null"); + AuthenticationPolicyUnion union = auths.getAdditionalProperties().get("basicAuth"); + assertNotNull(union, "basicAuth policy should be present"); + assertNotNull(union.getBasicAuthenticationPolicy(), "BasicAuthenticationPolicy should be set"); + } + + @Test + void testDoTaskSetAndForEach() { + Workflow wf = + WorkflowBuilder.workflow("flowDo") + .tasks( + d -> + d.set("initCtx", "$.foo = 'bar'") + .forEach("item", f -> f.each("item").at("$.list"))) + .build(); + + List items = wf.getDo(); + assertNotNull(items, "Do list must not be null"); + assertEquals(2, items.size(), "There should be two tasks"); + + TaskItem setItem = items.get(0); + assertEquals("initCtx", setItem.getName()); + SetTask st = setItem.getTask().getSetTask(); + assertNotNull(st, "SetTask should be present"); + assertEquals("$.foo = 'bar'", st.getSet().getString()); + + TaskItem forItem = items.get(1); + assertEquals("item", forItem.getName()); + assertNotNull(forItem.getTask().getForTask(), "ForTask should be present"); + } + + @Test + void testDoTaskMultipleTypes() { + Workflow wf = + WorkflowBuilder.workflow("flowMixed") + .tasks( + d -> + d.set("init", s -> s.expr("$.init = true")) + .forEach("items", f -> f.each("item").in("$.list")) + .switchCase( + "choice", + sw -> { + // no-op configuration + }) + .raise( + "alert", + r -> { + // no-op configuration + }) + .fork( + "parallel", + f -> { + // no-op configuration + })) + .build(); + + List items = wf.getDo(); + assertNotNull(items, "Do list must not be null"); + assertEquals(5, items.size(), "There should be five tasks"); + + // set task + TaskItem setItem = items.get(0); + assertEquals("init", setItem.getName()); + assertNotNull(setItem.getTask().getSetTask(), "SetTask should be present"); + + // forE task + TaskItem forItem = items.get(1); + assertEquals("items", forItem.getName()); + assertNotNull(forItem.getTask().getForTask(), "ForTask should be present"); + + // switchTask + TaskItem switchItem = items.get(2); + assertEquals("choice", switchItem.getName()); + assertNotNull(switchItem.getTask().getSwitchTask(), "SwitchTask should be present"); + + // raise task + TaskItem raiseItem = items.get(3); + assertEquals("alert", raiseItem.getName()); + assertNotNull(raiseItem.getTask().getRaiseTask(), "RaiseTask should be present"); + + // fork task + TaskItem forkItem = items.get(4); + assertEquals("parallel", forkItem.getName()); + assertNotNull(forkItem.getTask().getForkTask(), "ForkTask should be present"); + } + + @Test + void testDoTaskListenOne() { + Workflow wf = + WorkflowBuilder.workflow("flowListen") + .tasks( + d -> + d.listen( + "waitCheck", + l -> l.one(f -> f.with(p -> p.type("com.fake.pet").source("mySource"))))) + .build(); + + List items = wf.getDo(); + assertNotNull(items, "Do list must not be null"); + assertEquals(1, items.size(), "There should be one task"); + + TaskItem item = items.get(0); + assertEquals("waitCheck", item.getName()); + ListenTask lt = item.getTask().getListenTask(); + assertNotNull(lt, "ListenTask should be present"); + OneEventConsumptionStrategy one = lt.getListen().getTo().getOneEventConsumptionStrategy(); + assertNotNull(one, "One consumption strategy should be set"); + EventFilter filter = one.getOne(); + assertNotNull(filter, "EventFilter should be present"); + assertEquals("com.fake.pet", filter.getWith().getType(), "Filter type should match"); + } + + @Test + void testDoTaskEmitEvent() { + Workflow wf = + WorkflowBuilder.workflow("flowEmit") + .tasks( + d -> + d.emit( + "emitEvent", + e -> + e.event( + p -> + p.source(URI.create("https://petstore.com")) + .type("com.petstore.order.placed.v1") + .data( + Map.of( + "client", + Map.of( + "firstName", "Cruella", "lastName", "de Vil"), + "items", + List.of( + Map.of( + "breed", "dalmatian", "quantity", 101))))))) + .build(); + + List items = wf.getDo(); + assertNotNull(items, "Do list must not be null"); + assertEquals(1, items.size(), "There should be one emit task"); + + TaskItem item = items.get(0); + assertEquals("emitEvent", item.getName(), "TaskItem name should match"); + io.serverlessworkflow.api.types.EmitTask et = item.getTask().getEmitTask(); + assertNotNull(et, "EmitTask should be present"); + + io.serverlessworkflow.api.types.EmitEventDefinition ed = et.getEmit().getEvent(); + assertNotNull(ed, "EmitEventDefinition should be present"); + io.serverlessworkflow.api.types.EventProperties props = ed.getWith(); + assertEquals( + "https://petstore.com", + props.getSource().getUriTemplate().getLiteralUri().toString(), + "Source URI should match"); + assertEquals("com.petstore.order.placed.v1", props.getType(), "Event type should match"); + + Object dataObj = props.getData().getObject(); + assertNotNull(dataObj, "Data object should be present"); + assertInstanceOf(Map.class, dataObj, "Data should be a Map"); + @SuppressWarnings("unchecked") + Map dataMap = (Map) dataObj; + assertTrue(dataMap.containsKey("client"), "Data should contain 'client'"); + assertTrue(dataMap.containsKey("items"), "Data should contain 'items'"); + } + + @Test + void testDoTaskTryCatchWithRetry() { + Workflow wf = + WorkflowBuilder.workflow("flowTry") + .tasks( + d -> + d.tryCatch( + "tryBlock", + t -> + t.tryHandler(tb -> tb.set("init", s -> s.expr("$.start = true"))) + .catchHandler( + c -> + c.when("$.errorType == 'TEMP' ") + .retry( + r -> + r.when("$.retryCount < 3") + .limit( + l -> l.attempt(at -> at.count(3))))))) + .build(); + + List items = wf.getDo(); + assertEquals(1, items.size(), "There should be one try task"); + TaskItem item = items.get(0); + assertEquals("tryBlock", item.getName()); + + // Verify TryTask + TryTask tryTask = item.getTask().getTryTask(); + assertNotNull(tryTask, "TryTask should be present"); + + // Verify try handler tasks + List tryItems = tryTask.getTry(); + assertEquals(1, tryItems.size(), "Try handler should contain one task"); + TaskItem initItem = tryItems.get(0); + assertEquals("init", initItem.getName()); + assertNotNull(initItem.getTask().getSetTask(), "SetTask in try handler should be present"); + + // Verify catch configuration + TryTaskCatch catchCfg = tryTask.getCatch(); + assertNotNull(catchCfg, "Catch configuration should be present"); + assertEquals("$.errorType == 'TEMP' ", catchCfg.getWhen()); + + RetryPolicy retry = catchCfg.getRetry().getRetryPolicyDefinition(); + assertNotNull(retry, "RetryPolicy should be defined"); + assertEquals("$.retryCount < 3", retry.getWhen()); + RetryLimitAttempt attempt = retry.getLimit().getAttempt(); + assertEquals(3, attempt.getCount()); + } + + @Test + void testDoTaskTryCatchErrorsFiltering() { + Workflow wf = + WorkflowBuilder.workflow("flowCatch") + .tasks( + d -> + d.tryCatch( + "tryBlock", + t -> + t.tryHandler(tb -> tb.set("foo", s -> s.expr("$.foo = 'bar'"))) + .catchHandler( + c -> + c.exceptWhen("$.status == 500") + .errorsWith( + eb -> + eb.type("ServerError") + .status(500) + .instance("http://errors/5xx"))))) + .build(); + + TaskItem item = wf.getDo().get(0); + TryTask tryTask = item.getTask().getTryTask(); + TryTaskCatch catchCfg = tryTask.getCatch(); + + // exceptWhen should match + assertEquals("$.status == 500", catchCfg.getExceptWhen()); + + CatchErrors errors = catchCfg.getErrors(); + assertNotNull(errors, "CatchErrors should be present"); + ErrorFilter filter = errors.getWith(); + assertEquals("ServerError", filter.getType()); + assertEquals(500, filter.getStatus()); + assertEquals("http://errors/5xx", filter.getInstance()); + } + + @Test + void testWorkflowInputExternalSchema() { + String uri = "http://example.com/schema"; + Workflow wf = + WorkflowBuilder.workflow("wfInput").input(i -> i.from("$.data").schema(uri)).build(); + + assertNotNull(wf.getInput(), "Input must be set"); + assertEquals("$.data", wf.getInput().getFrom().getString()); + assertNotNull(wf.getInput().getSchema().getSchemaExternal(), "External schema must be set"); + String resolved = + wf.getInput() + .getSchema() + .getSchemaExternal() + .getResource() + .getEndpoint() + .getUriTemplate() + .getLiteralUri() + .toString(); + assertEquals(uri, resolved, "Schema URI should match"); + } + + @Test + void testWorkflowOutputExternalSchemaAndAs() { + String uri = "http://example.org/output-schema"; + Workflow wf = + WorkflowBuilder.workflow("wfOutput").output(o -> o.as("$.result").schema(uri)).build(); + + assertNotNull(wf.getOutput(), "Output must be set"); + assertEquals("$.result", wf.getOutput().getAs().getString()); + assertNotNull(wf.getOutput().getSchema().getSchemaExternal(), "External schema must be set"); + String resolved = + wf.getOutput() + .getSchema() + .getSchemaExternal() + .getResource() + .getEndpoint() + .getUriTemplate() + .getLiteralUri() + .toString(); + assertEquals(uri, resolved, "Schema URI should match"); + } + + @Test + void testWorkflowOutputInlineSchemaAndAsObject() { + Map inline = Map.of("foo", "bar"); + Workflow wf = + WorkflowBuilder.workflow().output(o -> o.as(Map.of("ok", true)).schema(inline)).build(); + + assertNotNull(wf.getOutput(), "Output must be set"); + assertInstanceOf(Map.class, wf.getOutput().getAs().getObject(), "As object must be a Map"); + assertNotNull(wf.getOutput().getSchema().getSchemaInline(), "Inline schema must be set"); + } + + @Test + void testWorkflowInputInlineSchemaAndFromObject() { + Map inline = Map.of("nested", List.of(1, 2, 3)); + Workflow wf = WorkflowBuilder.workflow().input(i -> i.from(inline).schema(inline)).build(); + + assertNotNull(wf.getInput(), "Input must be set"); + assertInstanceOf(Map.class, wf.getInput().getFrom().getObject(), "From object must be a Map"); + assertNotNull(wf.getInput().getSchema().getSchemaInline(), "Inline schema must be set"); + } + + @Test + void testDoTaskCallHTTPBasic() { + Workflow wf = + WorkflowBuilder.workflow("flowCallBasic") + .tasks( + d -> + d.callHTTP( + "basicCall", + c -> + c.method("POST") + .endpoint(URI.create("http://example.com/api")) + .body(Map.of("foo", "bar")))) + .build(); + List items = wf.getDo(); + assertEquals(1, items.size(), "Should have one HTTP call task"); + TaskItem ti = items.get(0); + assertEquals("basicCall", ti.getName()); + CallHTTP call = ti.getTask().getCallTask().getCallHTTP(); + assertNotNull(call, "CallHTTP should be present"); + assertEquals("POST", call.getWith().getMethod()); + assertEquals( + URI.create("http://example.com/api"), + call.getWith().getEndpoint().getUriTemplate().getLiteralUri()); + assertInstanceOf(Map.class, call.getWith().getBody(), "Body should be the Map provided"); + } + + @Test + void testDoTaskCallHTTPHeadersConsumerAndMap() { + Workflow wf = + WorkflowBuilder.workflow("flowCallHeaders") + .tasks( + d -> + d.callHTTP( + "hdrCall", + c -> + c.method("GET") + .endpoint("${uriExpr}") + .headers(h -> h.header("A", "1").header("B", "2")))) + .build(); + CallHTTP call = wf.getDo().get(0).getTask().getCallTask().getCallHTTP(); + HTTPHeaders hh = call.getWith().getHeaders().getHTTPHeaders(); + assertEquals("1", hh.getAdditionalProperties().get("A")); + assertEquals("2", hh.getAdditionalProperties().get("B")); + + Workflow wf2 = + WorkflowBuilder.workflow() + .tasks( + d -> + d.callHTTP( + c -> + c.method("GET").endpoint("expr").headers(Map.of("X", "10", "Y", "20")))) + .build(); + CallHTTP call2 = wf2.getDo().get(0).getTask().getCallTask().getCallHTTP(); + HTTPHeaders hh2 = call2.getWith().getHeaders().getHTTPHeaders(); + assertEquals("10", hh2.getAdditionalProperties().get("X")); + assertEquals("20", hh2.getAdditionalProperties().get("Y")); + } + + @Test + void testDoTaskCallHTTPQueryConsumerAndMap() { + Workflow wf = + WorkflowBuilder.workflow("flowCallQuery") + .tasks( + d -> + d.callHTTP( + "qryCall", + c -> + c.method("GET") + .endpoint("exprUri") + .query(q -> q.query("k1", "v1").query("k2", "v2")))) + .build(); + HTTPQuery hq = + wf.getDo().get(0).getTask().getCallTask().getCallHTTP().getWith().getQuery().getHTTPQuery(); + assertEquals("v1", hq.getAdditionalProperties().get("k1")); + assertEquals("v2", hq.getAdditionalProperties().get("k2")); + + Workflow wf2 = + WorkflowBuilder.workflow() + .tasks( + d -> + d.callHTTP( + c -> c.method("GET").endpoint("uri").query(Map.of("q1", "x", "q2", "y")))) + .build(); + HTTPQuery hq2 = + wf2.getDo() + .get(0) + .getTask() + .getCallTask() + .getCallHTTP() + .getWith() + .getQuery() + .getHTTPQuery(); + assertEquals("x", hq2.getAdditionalProperties().get("q1")); + assertEquals("y", hq2.getAdditionalProperties().get("q2")); + } + + @Test + void testDoTaskCallHTTPRedirectAndOutput() { + Workflow wf = + WorkflowBuilder.workflow("flowCallOpts") + .tasks( + d -> + d.callHTTP( + "optCall", + c -> + c.method("DELETE") + .endpoint("expr") + .redirect(true) + .output(HTTPArguments.HTTPOutput.RESPONSE))) + .build(); + CallHTTP call = wf.getDo().get(0).getTask().getCallTask().getCallHTTP(); + assertTrue(call.getWith().isRedirect(), "Redirect should be true"); + assertEquals( + HTTPArguments.HTTPOutput.RESPONSE, + call.getWith().getOutput(), + "Output should be overridden"); + } +} diff --git a/generators/jackson/pom.xml b/generators/jackson/pom.xml new file mode 100644 index 00000000..8bddaa5e --- /dev/null +++ b/generators/jackson/pom.xml @@ -0,0 +1,81 @@ + + 4.0.0 + maven-plugin + + io.serverlessworkflow + serverlessworkflow-generators + 8.0.0-SNAPSHOT + + serverless-workflow-jackson-generator + Serverless Workflow :: Generator:: Jackson + + 3.15.1 + + + + + + org.apache.maven.plugins + maven-plugin-plugin + ${maven-plugin-tools.version} + + jackson-generator + + + + help-mojo + + + helpmojo + + + + + + + + + + + + org.apache.maven.plugins + maven-plugin-report-plugin + ${maven-plugin-tools.version} + + + + + + org.apache.maven + maven-plugin-api + ${version.maven} + provided + + + com.sun.codemodel + codemodel + 2.6 + + + io.github.classgraph + classgraph + 4.8.181 + + + org.apache.maven.plugin-tools + maven-plugin-annotations + ${maven-plugin-tools.version} + provided + + + io.serverlessworkflow + serverlessworkflow-types + ${project.version} + + + io.serverlessworkflow + serverlessworkflow-serialization + ${project.version} + + + \ No newline at end of file diff --git a/generators/jackson/src/main/java/io/serverlessworkflow/generator/jackson/GeneratorUtils.java b/generators/jackson/src/main/java/io/serverlessworkflow/generator/jackson/GeneratorUtils.java new file mode 100644 index 00000000..5687de8c --- /dev/null +++ b/generators/jackson/src/main/java/io/serverlessworkflow/generator/jackson/GeneratorUtils.java @@ -0,0 +1,174 @@ +/* + * 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.generator.jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.sun.codemodel.JBlock; +import com.sun.codemodel.JClass; +import com.sun.codemodel.JClassAlreadyExistsException; +import com.sun.codemodel.JDefinedClass; +import com.sun.codemodel.JInvocation; +import com.sun.codemodel.JMethod; +import com.sun.codemodel.JMod; +import com.sun.codemodel.JPackage; +import com.sun.codemodel.JType; +import com.sun.codemodel.JVar; +import io.serverlessworkflow.serialization.DeserializeHelper; +import io.serverlessworkflow.serialization.SerializeHelper; +import java.io.IOException; +import java.util.Collection; +import java.util.List; + +public class GeneratorUtils { + + @FunctionalInterface + public interface SerializerFiller { + void accept(JMethod method, JVar valueParam, JVar genParam); + } + + @FunctionalInterface + public interface DeserializerFiller { + void accept(JMethod method, JVar parserParam); + } + + public static JDefinedClass serializerClass(JPackage jPackage, JClass relatedClass) + throws JClassAlreadyExistsException { + return createClass(jPackage, relatedClass, JsonSerializer.class, "Serializer"); + } + + public static JDefinedClass deserializerClass(JPackage jPackage, JClass relatedClass) + throws JClassAlreadyExistsException { + return createClass(jPackage, relatedClass, JsonDeserializer.class, "Deserializer"); + } + + public static void fillSerializer( + JDefinedClass definedClass, JClass relatedClass, SerializerFiller filler) { + JMethod method = definedClass.method(JMod.PUBLIC, void.class, "serialize"); + method.annotate(Override.class); + method._throws(IOException.class); + JVar valueParam = method.param(relatedClass, "value"); + JVar genParam = method.param(JsonGenerator.class, "gen"); + method.param(SerializerProvider.class, "serializers"); + filler.accept(method, valueParam, genParam); + } + + public static void fillDeserializer( + JDefinedClass definedClass, JClass relatedClass, DeserializerFiller filler) { + JMethod method = definedClass.method(JMod.PUBLIC, relatedClass, "deserialize"); + method.annotate(Override.class); + method._throws(IOException.class); + JVar parserParam = method.param(JsonParser.class, "parser"); + method.param(DeserializationContext.class, "dctx"); + filler.accept(method, parserParam); + } + + private static JDefinedClass createClass( + JPackage jPackage, JClass relatedClass, Class serializerClass, String suffix) + throws JClassAlreadyExistsException { + JDefinedClass definedClass = jPackage._class(JMod.NONE, relatedClass.name() + suffix); + definedClass._extends(definedClass.owner().ref(serializerClass).narrow(relatedClass)); + return definedClass; + } + + public static JDefinedClass generateSerializer(JPackage jPackage, JClass relatedClass) + throws JClassAlreadyExistsException { + JDefinedClass definedClass = GeneratorUtils.serializerClass(jPackage, relatedClass); + GeneratorUtils.fillSerializer( + definedClass, + relatedClass, + (method, valueParam, genParam) -> + method + .body() + .staticInvoke(definedClass.owner().ref(SerializeHelper.class), "serializeOneOf") + .arg(genParam) + .arg(valueParam)); + return definedClass; + } + + public static JDefinedClass generateDeserializer( + JPackage jPackage, JClass relatedClass, Collection oneOfTypes) + throws JClassAlreadyExistsException { + JDefinedClass definedClass = GeneratorUtils.deserializerClass(jPackage, relatedClass); + GeneratorUtils.fillDeserializer( + definedClass, + relatedClass, + (method, parserParam) -> { + JBlock body = method.body(); + + body._return( + definedClass + .owner() + .ref(DeserializeHelper.class) + .staticInvoke("deserializeOneOf") + .arg(parserParam) + .arg(relatedClass.dotclass()) + .arg(list(definedClass, oneOfTypes))); + }); + return definedClass; + } + + public static JDefinedClass generateDeserializer( + JPackage jPackage, JClass relatedClass, JType propertyType) + throws JClassAlreadyExistsException { + JDefinedClass definedClass = GeneratorUtils.deserializerClass(jPackage, relatedClass); + GeneratorUtils.fillDeserializer( + definedClass, + relatedClass, + (method, parserParam) -> + method + .body() + ._return( + definedClass + .owner() + .ref(DeserializeHelper.class) + .staticInvoke("deserializeItem") + .arg(parserParam) + .arg(relatedClass.dotclass()) + .arg(((JClass) propertyType).dotclass()))); + return definedClass; + } + + public static JDefinedClass generateSerializer( + JPackage jPackage, JClass relatedClass, String keyMethod, String valueMethod) + throws JClassAlreadyExistsException { + JDefinedClass definedClass = GeneratorUtils.serializerClass(jPackage, relatedClass); + GeneratorUtils.fillSerializer( + definedClass, + relatedClass, + (method, valueParam, genParam) -> { + JBlock body = method.body(); + body.invoke(genParam, "writeStartObject"); + body.invoke(genParam, "writeObjectField") + .arg(valueParam.invoke(keyMethod)) + .arg(valueParam.invoke(valueMethod)); + body.invoke(genParam, "writeEndObject"); + }); + return definedClass; + } + + private static JInvocation list(JDefinedClass definedClass, Collection list) { + JInvocation result = definedClass.owner().ref(List.class).staticInvoke("of"); + list.forEach(c -> result.arg(c.dotclass())); + return result; + } + + private GeneratorUtils() {} +} diff --git a/generators/jackson/src/main/java/io/serverlessworkflow/generator/jackson/JacksonMixInPojo.java b/generators/jackson/src/main/java/io/serverlessworkflow/generator/jackson/JacksonMixInPojo.java new file mode 100644 index 00000000..dc84e9ed --- /dev/null +++ b/generators/jackson/src/main/java/io/serverlessworkflow/generator/jackson/JacksonMixInPojo.java @@ -0,0 +1,245 @@ +/* + * 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.generator.jackson; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.databind.Module.SetupContext; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.sun.codemodel.JClass; +import com.sun.codemodel.JClassAlreadyExistsException; +import com.sun.codemodel.JCodeModel; +import com.sun.codemodel.JDefinedClass; +import com.sun.codemodel.JExpr; +import com.sun.codemodel.JExpression; +import com.sun.codemodel.JMethod; +import com.sun.codemodel.JMod; +import com.sun.codemodel.JPackage; +import io.github.classgraph.AnnotationClassRef; +import io.github.classgraph.AnnotationInfo; +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ClassInfo; +import io.github.classgraph.ClassRefTypeSignature; +import io.github.classgraph.MethodInfo; +import io.github.classgraph.ScanResult; +import io.github.classgraph.TypeArgument; +import io.github.classgraph.TypeSignature; +import io.serverlessworkflow.annotations.AdditionalProperties; +import io.serverlessworkflow.annotations.Item; +import io.serverlessworkflow.annotations.ItemKey; +import io.serverlessworkflow.annotations.ItemValue; +import io.serverlessworkflow.annotations.Union; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.lang.annotation.Annotation; +import java.nio.file.Files; +import java.util.Collection; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; + +@Mojo( + name = "generate", + defaultPhase = LifecyclePhase.GENERATE_SOURCES, + requiresDependencyResolution = ResolutionScope.COMPILE, + threadSafe = true) +public class JacksonMixInPojo extends AbstractMojo { + + @Parameter( + property = "jacksonmixinpojo.outputDirectory", + defaultValue = "${project.build.directory}/generated-sources/jacksonmixinpojo") + private File outputDirectory; + + @Parameter(property = "jacksonmixinpojo.srcPackage") + private String srcPackage; + + @Parameter(property = "jacksonmixinpojo.targetPackage") + private String targetPackage; + + private static final String MIXIN_METHOD = "setMixInAnnotation"; + private static final String ADD_PROPERTIES_METHOD = "getAdditionalProperties"; + private static final String SETUP_METHOD = "setupModule"; + private JCodeModel codeModel; + private JPackage rootPackage; + private JMethod setupMethod; + + @FunctionalInterface + interface AnnotationProcessor { + void accept(ClassInfo classInfo, JDefinedClass definedClass) + throws JClassAlreadyExistsException; + } + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + + try (ScanResult result = + new ClassGraph() + .enableAnnotationInfo() + .enableMethodInfo() + .acceptPackages(srcPackage) + .scan()) { + codeModel = new JCodeModel(); + rootPackage = codeModel._package(targetPackage); + setupMethod = + rootPackage + ._class("JacksonMixInModule") + ._extends(SimpleModule.class) + .method(JMod.PUBLIC, codeModel.VOID, SETUP_METHOD); + processAnnotatedClasses(result, Union.class, this::buildUnionMixIn); + processAnnotatedClasses(result, AdditionalProperties.class, this::buildAdditionalPropsMixIn); + processAnnotatedClasses(result, Item.class, this::buildItemMixIn); + processAnnotatedClasses(result.getAllEnums(), this::buildEnumMixIn); + setupMethod + .body() + .invoke(JExpr._super(), SETUP_METHOD) + .arg(setupMethod.param(SetupContext.class, "context")); + Files.createDirectories(outputDirectory.toPath()); + codeModel.build(outputDirectory, (PrintStream) null); + } catch (JClassAlreadyExistsException | IOException e) { + getLog().error(e); + } + } + + private void processAnnotatedClasses( + Iterable classesInfo, AnnotationProcessor processor) + throws JClassAlreadyExistsException { + for (ClassInfo classInfo : classesInfo) { + setupMethod + .body() + .invoke(JExpr._super(), MIXIN_METHOD) + .arg(JExpr.dotclass(codeModel.ref(classInfo.getName()))) + .arg(processAnnotatedClass(classInfo, processor)); + } + } + + private void processAnnotatedClasses( + ScanResult result, Class annotation, AnnotationProcessor processor) + throws JClassAlreadyExistsException { + processAnnotatedClasses(result.getClassesWithAnnotation(annotation), processor); + } + + private JExpression processAnnotatedClass(ClassInfo classInfo, AnnotationProcessor processor) + throws JClassAlreadyExistsException { + JDefinedClass result = createMixInClass(classInfo); + processor.accept(classInfo, result); + return JExpr.dotclass(result); + } + + private void buildAdditionalPropsMixIn( + ClassInfo addPropsClassInfo, JDefinedClass addPropsMixClass) { + JClass mapStringObject = + getReturnType(addPropsClassInfo.getMethodInfo().getSingleMethod(ADD_PROPERTIES_METHOD)); + JClass objectType = mapStringObject.getTypeParameters().get(1); + addPropsMixClass + .method(JMod.ABSTRACT, mapStringObject, ADD_PROPERTIES_METHOD) + .annotate(JsonAnyGetter.class); + addPropsMixClass + .field(JMod.NONE, mapStringObject, "additionalProperties") + .annotate(JsonIgnore.class); + JMethod setter = + addPropsMixClass.method(JMod.ABSTRACT, codeModel.VOID, "setAdditionalProperty"); + setter.param(String.class, "name"); + setter.param(objectType, "value"); + setter.annotate(JsonAnySetter.class); + } + + private void buildItemMixIn(ClassInfo classInfo, JDefinedClass mixClass) + throws JClassAlreadyExistsException { + JClass relClass = codeModel.ref(classInfo.getName()); + MethodInfo keyMethod = + classInfo.getMethodInfo().filter(m -> m.hasAnnotation(ItemKey.class)).get(0); + MethodInfo valueMethod = + classInfo.getMethodInfo().filter(m -> m.hasAnnotation(ItemValue.class)).get(0); + mixClass + .annotate(JsonSerialize.class) + .param( + "using", + GeneratorUtils.generateSerializer( + rootPackage, relClass, keyMethod.getName(), valueMethod.getName())); + mixClass + .annotate(JsonDeserialize.class) + .param( + "using", + GeneratorUtils.generateDeserializer(rootPackage, relClass, getReturnType(valueMethod))); + } + + private void buildUnionMixIn(ClassInfo unionClassInfo, JDefinedClass unionMixClass) + throws JClassAlreadyExistsException { + JClass unionClass = codeModel.ref(unionClassInfo.getName()); + unionMixClass + .annotate(JsonSerialize.class) + .param("using", GeneratorUtils.generateSerializer(rootPackage, unionClass)); + unionMixClass + .annotate(JsonDeserialize.class) + .param( + "using", + GeneratorUtils.generateDeserializer( + rootPackage, unionClass, getUnionClasses(unionClassInfo))); + } + + private void buildEnumMixIn(ClassInfo classInfo, JDefinedClass mixClass) + throws JClassAlreadyExistsException { + mixClass.method(JMod.ABSTRACT, String.class, "value").annotate(JsonValue.class); + + JMethod staticMethod = + mixClass.method(JMod.STATIC, codeModel.ref(classInfo.getName()), "fromValue"); + staticMethod.param(String.class, "value"); + staticMethod.annotate(JsonCreator.class); + staticMethod.body()._return(JExpr._null()); + } + + private JDefinedClass createMixInClass(ClassInfo classInfo) throws JClassAlreadyExistsException { + return rootPackage._class(JMod.ABSTRACT, classInfo.getSimpleName() + "MixIn"); + } + + private Collection getUnionClasses(ClassInfo unionClassInfo) { + AnnotationInfo info = unionClassInfo.getAnnotationInfoRepeatable(Union.class).get(0); + Object[] unionClasses = (Object[]) info.getParameterValues().getValue("value"); + return Stream.of(unionClasses) + .map(AnnotationClassRef.class::cast) + .map(ref -> codeModel.ref(ref.getName())) + .collect(Collectors.toList()); + } + + private JClass getReturnType(MethodInfo info) { + return getReturnType(info.getTypeSignatureOrTypeDescriptor().getResultType()); + } + + private JClass getReturnType(ClassRefTypeSignature refTypeSignature) { + JClass result = codeModel.ref(refTypeSignature.getFullyQualifiedClassName()); + for (TypeArgument t : refTypeSignature.getTypeArguments()) + result = result.narrow(getReturnType(t.getTypeSignature())); + return result; + } + + private JClass getReturnType(TypeSignature t) { + return t instanceof ClassRefTypeSignature refTypeSignature + ? getReturnType(refTypeSignature) + : codeModel.ref(t.toString()); + } +} diff --git a/generators/pom.xml b/generators/pom.xml new file mode 100644 index 00000000..9ad258c4 --- /dev/null +++ b/generators/pom.xml @@ -0,0 +1,15 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-parent + 8.0.0-SNAPSHOT + + serverlessworkflow-generators + Serverless Workflow :: Generators + pom + + jackson + types + + \ No newline at end of file diff --git a/generators/types/pom.xml b/generators/types/pom.xml new file mode 100644 index 00000000..232009ea --- /dev/null +++ b/generators/types/pom.xml @@ -0,0 +1,46 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-generators + 8.0.0-SNAPSHOT + + serverless-workflow-types-generator + Serverless Workflow :: Generator:: Types + + + org.jsonschema2pojo + jsonschema2pojo-core + + + io.serverlessworkflow + serverlessworkflow-annotations + ${project.version} + + + + + + + com.spotify.fmt + fmt-maven-plugin + + src/main/java + src/test/java + false + .*\.java + false + false + + + + + + format + + + + + + + \ No newline at end of file diff --git a/generators/types/src/main/java/io/serverlessworkflow/generator/AllAnyOneOfSchemaRule.java b/generators/types/src/main/java/io/serverlessworkflow/generator/AllAnyOneOfSchemaRule.java new file mode 100644 index 00000000..a9823ce6 --- /dev/null +++ b/generators/types/src/main/java/io/serverlessworkflow/generator/AllAnyOneOfSchemaRule.java @@ -0,0 +1,658 @@ +/* + * 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.generator; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.sun.codemodel.JAnnotationArrayMember; +import com.sun.codemodel.JBlock; +import com.sun.codemodel.JClass; +import com.sun.codemodel.JClassAlreadyExistsException; +import com.sun.codemodel.JClassContainer; +import com.sun.codemodel.JConditional; +import com.sun.codemodel.JDefinedClass; +import com.sun.codemodel.JExpr; +import com.sun.codemodel.JFieldVar; +import com.sun.codemodel.JInvocation; +import com.sun.codemodel.JMethod; +import com.sun.codemodel.JMod; +import com.sun.codemodel.JPackage; +import com.sun.codemodel.JType; +import com.sun.codemodel.JVar; +import io.serverlessworkflow.annotations.OneOfSetter; +import io.serverlessworkflow.annotations.OneOfValueProvider; +import io.serverlessworkflow.annotations.Union; +import jakarta.validation.ConstraintViolationException; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; +import org.jsonschema2pojo.Jsonschema2Pojo; +import org.jsonschema2pojo.Schema; +import org.jsonschema2pojo.exception.GenerationException; +import org.jsonschema2pojo.rules.RuleFactory; +import org.jsonschema2pojo.rules.SchemaRule; + +class AllAnyOneOfSchemaRule extends SchemaRule { + + private static final String ALL_OF = "allOf"; + private RuleFactory ruleFactory; + + AllAnyOneOfSchemaRule(RuleFactory ruleFactory) { + super(ruleFactory); + this.ruleFactory = ruleFactory; + } + + private static final String REF = "$ref"; + private static final String TITLE = "title"; + private static final String PATTERN = "pattern"; + + private enum Format { + URI_TEMPLATE("^[A-Za-z][A-Za-z0-9+\\-.]*://.*"); + + private final String pattern; + + Format(String pattern) { + this.pattern = pattern; + } + + public static Format parse(String str) { + if (str != null) { + switch (str) { + case "uri-template": + return URI_TEMPLATE; + } + } + return null; + } + + String pattern() { + + return pattern; + } + } + + private static class JTypeWrapper implements Comparable { + + private final JType type; + private final JsonNode node; + + public JTypeWrapper(JType type, JsonNode node) { + this.type = type; + this.node = node; + } + + public JType getType() { + return type; + } + + public JsonNode getNode() { + return node; + } + + @Override + public int compareTo(JTypeWrapper other) { + return typeToNumber() - other.typeToNumber(); + } + + private int typeToNumber() { + if (type.name().equals("Object")) { + return 6; + } else if (type.name().equals("String")) { + return node.has(PATTERN) || node.has(REF) ? 4 : 5; + } else if (type.isPrimitive()) { + return 3; + } else if (type.isReference()) { + return 2; + } else if (type.isArray()) { + return 1; + } else { + return 0; + } + } + } + + @Override + public JType apply( + String nodeName, + JsonNode schemaNode, + JsonNode parent, + JClassContainer generatableType, + Schema schema) { + + Optional refType = refType(nodeName, schemaNode, parent, generatableType, schema); + List oneOfTypes = new ArrayList<>(); + List allOfTypes = new ArrayList<>(); + + unionType("oneOf", nodeName, schemaNode, parent, generatableType, schema, oneOfTypes); + unionType("anyOf", nodeName, schemaNode, parent, generatableType, schema, oneOfTypes); + allOfType(nodeName, schemaNode, parent, generatableType, schema, allOfTypes); + + Collections.sort(oneOfTypes); + + JType javaType; + if (schemaNode.has("enum")) { + javaType = + ruleFactory.getEnumRule().apply(nodeName, schemaNode, parent, generatableType, schema); + } else if (!schemaNode.has("properties") + && oneOfTypes.isEmpty() + && allOfTypes.isEmpty() + && refType.isPresent()) { + javaType = refType.get(); + } else if (!schemaNode.has("properties") + && oneOfTypes.isEmpty() + && allOfTypes.size() == 1 + && refType.isEmpty()) { + javaType = allOfTypes.get(0).getType(); + } else if (!schemaNode.has("properties") + && oneOfTypes.size() == 1 + && allOfTypes.isEmpty() + && refType.isEmpty()) { + javaType = oneOfTypes.get(0).getType(); + } else { + JPackage container = generatableType.getPackage(); + javaType = ruleFactory.getTypeRule().apply(nodeName, schemaNode, parent, container, schema); + if (javaType instanceof JDefinedClass) { + javaType = + populateAllOf( + schema, populateRef((JDefinedClass) javaType, refType, schema), allOfTypes); + } + if (!oneOfTypes.isEmpty()) { + try { + JDefinedClass unionClass; + Optional commonType; + if (javaType instanceof JDefinedClass) { + JDefinedClass clazz = (JDefinedClass) javaType; + if (clazz.methods().isEmpty()) { + unionClass = clazz; + commonType = Optional.empty(); + } else { + unionClass = container._class(clazz.name() + "Union"); + commonType = Optional.of(clazz); + } + } else { + unionClass = + container._class( + ruleFactory + .getNameHelper() + .getUniqueClassName(nodeName, schemaNode, container)); + commonType = Optional.empty(); + } + javaType = populateOneOf(schema, unionClass, commonType, oneOfTypes); + } catch (JClassAlreadyExistsException ex) { + throw new IllegalStateException(ex); + } + } + schema.setJavaType(javaType); + } + return javaType; + } + + private void allOfType( + String nodeName, + JsonNode schemaNode, + JsonNode parent, + JClassContainer generatableType, + Schema schema, + List allOfTypes) { + if (schemaNode.has(ALL_OF)) { + ArrayNode array = (ArrayNode) schemaNode.get(ALL_OF); + if (array.size() == 2) { + JsonNode refNode = null; + JsonNode propsNode = null; + int refNodePos = 0; + int propsNodePos = 0; + int pos = 0; + for (JsonNode node : array) { + if (node.isObject() && node.size() == 1) { + if (node.has(REF)) { + refNode = node; + refNodePos = pos++; + } else if (node.has("properties")) { + propsNode = node; + propsNodePos = pos++; + } else { + pos++; + break; + } + } + } + if (refNode != null && propsNode != null) { + allOfTypes.add( + new JTypeWrapper( + inheritanceNode( + nodeName, + schemaNode, + generatableType, + schema, + refNode, + refNodePos, + propsNode, + propsNodePos), + array)); + return; + } + } + unionType(ALL_OF, nodeName, schemaNode, parent, generatableType, schema, allOfTypes); + } + } + + private JType inheritanceNode( + String nodeName, + JsonNode schemaNode, + JClassContainer container, + Schema schema, + JsonNode refNode, + int refNodePos, + JsonNode propsNode, + int propsNodePos) { + try { + JDefinedClass javaType = + container._class( + ruleFactory + .getNameHelper() + .getUniqueClassName(nodeName, schemaNode, container.getPackage())); + javaType._extends( + (JClass) + refType( + refNode.get(REF).asText(), + nodeName, + refNode, + schemaNode, + container, + childSchema(schema, ALL_OF, refNodePos))); + ruleFactory + .getPropertiesRule() + .apply( + nodeName, + propsNode.get("properties"), + propsNode, + javaType, + childSchema(schema, ALL_OF, propsNodePos)); + return javaType; + } catch (JClassAlreadyExistsException e) { + throw new IllegalStateException(e); + } + } + + private Schema childSchema(Schema parentSchema, String prefix, int pos) { + String ref = parentSchema.getId().toString() + '/' + prefix + '/' + pos; + return ruleFactory + .getSchemaStore() + .create(URI.create(ref), ruleFactory.getGenerationConfig().getRefFragmentPathDelimiters()); + } + + private JDefinedClass populateAllOf( + Schema parentSchema, JDefinedClass definedClass, Collection allOfTypes) { + return wrapAll(parentSchema, definedClass, Optional.empty(), allOfTypes, Optional.empty()); + } + + private JDefinedClass populateOneOf( + Schema parentSchema, + JDefinedClass definedClass, + Optional commonType, + Collection oneOfTypes) { + + JFieldVar valueField = + definedClass.field( + JMod.PRIVATE, + commonType.orElse(definedClass.owner().ref(Object.class)), + ruleFactory.getNameHelper().getPropertyName("value", null), + null); + definedClass._implements( + definedClass.owner().ref(OneOfValueProvider.class).narrow(valueField.type())); + GeneratorUtils.implementInterface(definedClass, valueField); + JAnnotationArrayMember unionAnnotation = definedClass.annotate(Union.class).paramArray("value"); + oneOfTypes.forEach(t -> unionAnnotation.param(t.getType())); + return wrapAll(parentSchema, definedClass, commonType, oneOfTypes, Optional.of(valueField)); + } + + private JDefinedClass wrapAll( + Schema parentSchema, + JDefinedClass definedClass, + Optional commonType, + Collection types, + Optional valueField) { + Collection stringTypes = new ArrayList<>(); + for (JTypeWrapper unionType : types) { + if (isStringType(unionType.getType())) { + stringTypes.add(unionType); + } else { + if (unionType.getType() instanceof JDefinedClass) { + commonType.ifPresent( + c -> ((JDefinedClass) unionType.getType())._extends((JDefinedClass) c)); + } + + wrapIt(parentSchema, definedClass, valueField, unionType.getType(), unionType.getNode()); + } + } + if (!stringTypes.isEmpty()) { + wrapStrings(parentSchema, definedClass, valueField, stringTypes); + } + return definedClass; + } + + private JDefinedClass populateRef( + JDefinedClass definedClass, Optional refType, Schema parentSchema) { + refType.ifPresent( + type -> { + if (type instanceof JClass) { + definedClass._extends((JClass) type); + } else { + wrapIt(parentSchema, definedClass, Optional.empty(), type, null); + } + }); + + if (definedClass.constructors().hasNext() + && definedClass.getConstructor(new JType[0]) == null) { + definedClass.constructor(JMod.PUBLIC); + } + return definedClass; + } + + private static boolean isStringType(JType type) { + return type.name().equals("String"); + } + + private void wrapIt( + Schema parentSchema, + JDefinedClass definedClass, + Optional valueField, + JType unionType, + JsonNode node) { + String typeName = getTypeName(node, unionType, parentSchema); + JFieldVar instanceField = + getInstanceField(typeName, parentSchema, definedClass, unionType, node); + JMethod method = getSetterMethod(typeName, definedClass, instanceField, node); + method + .body() + .assign( + JExpr._this().ref(instanceField), + setupMethod(definedClass, method, valueField, instanceField)); + } + + private JVar setupMethod( + JDefinedClass definedClass, + JMethod method, + Optional valueField, + JFieldVar instanceField) { + JVar methodParam = method.param(instanceField.type(), instanceField.name()); + valueField.ifPresent( + v -> { + method.body().assign(JExpr._this().ref(v), methodParam); + method + .annotate(definedClass.owner().ref(OneOfSetter.class)) + .param("value", instanceField.type()); + }); + return methodParam; + } + + private JMethod getSetterMethod( + String fieldName, JDefinedClass definedClass, JFieldVar instanceField, JsonNode node) { + String setterName = ruleFactory.getNameHelper().getSetterName(fieldName, node); + JMethod fluentMethod = + definedClass.method(JMod.PUBLIC, definedClass, setterName.replaceFirst("set", "with")); + JBlock body = fluentMethod.body(); + body.assign(instanceField, fluentMethod.param(instanceField.type(), "value")); + body._return(JExpr._this()); + return definedClass.method(JMod.PUBLIC, definedClass.owner().VOID, setterName); + } + + private void wrapStrings( + Schema parentSchema, + JDefinedClass definedClass, + Optional valueField, + Collection stringTypes) { + Iterator iter = stringTypes.iterator(); + JTypeWrapper first = iter.next(); + String pattern = pattern(first.getNode(), parentSchema); + if (pattern == null && iter.hasNext()) { + pattern = ".*"; + } + String typeName = getTypeName(first.getNode(), first.getType(), parentSchema); + JFieldVar instanceField = + getInstanceField(typeName, parentSchema, definedClass, first.getType(), first.getNode()); + JMethod setterMethod = getSetterMethod(typeName, definedClass, instanceField, first.getNode()); + JVar methodParam = setupMethod(definedClass, setterMethod, valueField, instanceField); + JBlock body = setterMethod.body(); + if (pattern != null) { + JConditional condition = + body._if(getPatternCondition(pattern, body, instanceField, methodParam, definedClass)); + condition._then().assign(JExpr._this().ref(instanceField), methodParam); + while (iter.hasNext()) { + JTypeWrapper item = iter.next(); + instanceField = + getInstanceField( + getTypeName(item.getNode(), item.getType(), parentSchema), + parentSchema, + definedClass, + item.getType(), + item.getNode()); + pattern = pattern(item.getNode(), parentSchema); + if (pattern == null) { + pattern = ".*"; + } + condition = + condition._elseif( + getPatternCondition(pattern, body, instanceField, methodParam, definedClass)); + condition._then().assign(JExpr._this().ref(instanceField), methodParam); + } + condition + ._else() + ._throw( + JExpr._new(definedClass.owner()._ref(ConstraintViolationException.class)) + .arg( + definedClass + .owner() + .ref(String.class) + .staticInvoke("format") + .arg("%s does not match any pattern") + .arg(methodParam)) + .arg(JExpr._null())); + } else { + body.assign(JExpr._this().ref(instanceField), methodParam); + } + } + + private JFieldVar getInstanceField( + String fieldName, + Schema parentSchema, + JDefinedClass definedClass, + JType type, + JsonNode node) { + JFieldVar instanceField = + definedClass.field( + JMod.PRIVATE, type, ruleFactory.getNameHelper().getPropertyName(fieldName, node)); + GeneratorUtils.getterMethod( + definedClass, instanceField, ruleFactory.getNameHelper(), fieldName); + return instanceField; + } + + private static String getFromNode(JsonNode node, String fieldName) { + if (node != null) { + JsonNode item = node.get(fieldName); + if (item != null) { + return item.asText(); + } + } + + return null; + } + + private JInvocation getPatternCondition( + String pattern, + JBlock body, + JFieldVar instanceField, + JVar instanceParam, + JDefinedClass definedClass) { + JFieldVar patternField = + definedClass.field( + JMod.PRIVATE | JMod.STATIC | JMod.FINAL, + Pattern.class, + instanceField.name() + "_" + "Pattern", + definedClass.owner().ref(Pattern.class).staticInvoke("compile").arg(pattern)); + return JExpr.invoke(JExpr.invoke(patternField, "matcher").arg(instanceParam), "matches"); + } + + private void unionType( + String prefix, + String nodeName, + JsonNode schemaNode, + JsonNode parent, + JClassContainer generatableType, + Schema parentSchema, + Collection types) { + if (schemaNode.has(prefix)) { + ArrayNode array = (ArrayNode) schemaNode.get(prefix); + if (schemaNode.has(TITLE)) { + nodeName = schemaNode.get(TITLE).asText(); + } + int i = 0; + for (JsonNode oneOf : array) { + if (!ignoreNode(oneOf)) { + Schema schema = childSchema(parentSchema, prefix, i++); + types.add( + new JTypeWrapper( + schema.isGenerated() + ? schema.getJavaType() + : apply(nodeName, oneOf, parent, generatableType.getPackage(), schema), + oneOf)); + } + } + } + } + + private static boolean ignoreNode(JsonNode node) { + return allRequired(node) || allRemoveProperties(node); + } + + private static boolean allRemoveProperties(JsonNode node) { + if (node.size() == 1 && node.has("properties")) { + JsonNode propsNode = node.get("properties"); + for (JsonNode propNode : propsNode) { + if (!propNode.isBoolean() || propNode.asBoolean()) { + return false; + } + } + return true; + } + return false; + } + + private static boolean allRequired(JsonNode node) { + return node.size() == 1 && node.has("required"); + } + + private Optional refType( + String nodeName, + JsonNode schemaNode, + JsonNode parent, + JClassContainer container, + Schema parentSchema) { + return schemaNode.has(REF) + ? Optional.of( + refType( + schemaNode.get(REF).asText(), + nodeName, + schemaNode, + parent, + container, + parentSchema)) + : Optional.empty(); + } + + private JType refType( + String ref, + String nodeName, + JsonNode schemaNode, + JsonNode parent, + JClassContainer container, + Schema parentSchema) { + Schema schema = + ruleFactory + .getSchemaStore() + .create( + parentSchema, + ref, + ruleFactory.getGenerationConfig().getRefFragmentPathDelimiters()); + + return schema.isGenerated() + ? schema.getJavaType() + : apply( + nameFromRef(ref, nodeName, schemaNode), schema.getContent(), parent, container, schema); + } + + private JsonNode schemaRef(JsonNode schemaNode, Schema parentSchema) { + String ref = getFromNode(schemaNode, REF); + return ref != null + ? ruleFactory + .getSchemaStore() + .create( + parentSchema, ref, ruleFactory.getGenerationConfig().getRefFragmentPathDelimiters()) + .getContent() + : null; + } + + private String getTypeName(JsonNode node, JType type, Schema parentSchema) { + final String title = "title"; + String name = getFromNode(node, title); + if (name == null) { + name = getFromNode(schemaRef(node, parentSchema), title); + } + if (name == null) { + name = type.name(); + } + return name; + } + + private String pattern(JsonNode node, Schema parentSchema) { + String pattern = pattern(node); + return pattern != null ? pattern : pattern(schemaRef(node, parentSchema)); + } + + private String pattern(JsonNode node) { + Format format = Format.parse(getFromNode(node, "format")); + return format != null ? format.pattern() : getFromNode(node, PATTERN); + } + + private String nameFromRef(String ref, String nodeName, JsonNode schemaNode) { + if (schemaNode.has(TITLE)) { + return schemaNode.get(TITLE).asText(); + } + if ("#".equals(ref)) { + return nodeName; + } + String nameFromRef; + if (!ref.contains("#")) { + nameFromRef = Jsonschema2Pojo.getNodeName(ref, ruleFactory.getGenerationConfig()); + } else { + String[] nameParts = ref.split("[/\\#]"); + nameFromRef = nameParts[nameParts.length - 1]; + } + + try { + return URLDecoder.decode(nameFromRef, "utf-8"); + } catch (UnsupportedEncodingException e) { + throw new GenerationException("Failed to decode ref: " + ref, e); + } + } +} diff --git a/generators/types/src/main/java/io/serverlessworkflow/generator/CustomAnnotator.java b/generators/types/src/main/java/io/serverlessworkflow/generator/CustomAnnotator.java new file mode 100644 index 00000000..657ba3ce --- /dev/null +++ b/generators/types/src/main/java/io/serverlessworkflow/generator/CustomAnnotator.java @@ -0,0 +1,46 @@ +/* + * 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.generator; + +import com.fasterxml.jackson.databind.JsonNode; +import com.sun.codemodel.JDefinedClass; +import com.sun.codemodel.JFieldVar; +import io.serverlessworkflow.annotations.AdditionalProperties; +import jakarta.validation.constraints.Pattern; +import org.jsonschema2pojo.AbstractAnnotator; +import org.jsonschema2pojo.GenerationConfig; + +public class CustomAnnotator extends AbstractAnnotator { + + private static final String CONST = "const"; + + public CustomAnnotator(GenerationConfig generationConfig) { + super(generationConfig); + } + + @Override + public void additionalPropertiesField(JFieldVar field, JDefinedClass clazz, String propertyName) { + clazz.annotate(AdditionalProperties.class); + } + + @Override + public void propertyField( + JFieldVar field, JDefinedClass clazz, String propertyName, JsonNode propertyNode) { + if (propertyNode.has(CONST)) { + field.annotate(Pattern.class).param("regexp", propertyNode.get(CONST).asText()); + } + } +} diff --git a/generators/types/src/main/java/io/serverlessworkflow/generator/EmptyObjectTypeRule.java b/generators/types/src/main/java/io/serverlessworkflow/generator/EmptyObjectTypeRule.java new file mode 100644 index 00000000..45dd24f2 --- /dev/null +++ b/generators/types/src/main/java/io/serverlessworkflow/generator/EmptyObjectTypeRule.java @@ -0,0 +1,46 @@ +/* + * 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.generator; + +import com.fasterxml.jackson.databind.JsonNode; +import com.sun.codemodel.JClassContainer; +import com.sun.codemodel.JType; +import org.jsonschema2pojo.Schema; +import org.jsonschema2pojo.rules.RuleFactory; +import org.jsonschema2pojo.rules.TypeRule; + +public class EmptyObjectTypeRule extends TypeRule { + + protected EmptyObjectTypeRule(RuleFactory ruleFactory) { + super(ruleFactory); + } + + @Override + public JType apply( + String nodeName, + JsonNode node, + JsonNode parent, + JClassContainer generatableType, + Schema currentSchema) { + return isEmptyObject(node) + ? generatableType.owner().ref(Object.class) + : super.apply(nodeName, node, parent, generatableType, currentSchema); + } + + private boolean isEmptyObject(JsonNode node) { + return node.size() == 1 && node.has("type") && node.get("type").asText().equals("object"); + } +} diff --git a/generators/types/src/main/java/io/serverlessworkflow/generator/GeneratorUtils.java b/generators/types/src/main/java/io/serverlessworkflow/generator/GeneratorUtils.java new file mode 100644 index 00000000..abcf40eb --- /dev/null +++ b/generators/types/src/main/java/io/serverlessworkflow/generator/GeneratorUtils.java @@ -0,0 +1,45 @@ +/* + * 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.generator; + +import com.sun.codemodel.JDefinedClass; +import com.sun.codemodel.JFieldVar; +import com.sun.codemodel.JMethod; +import com.sun.codemodel.JMod; +import org.jsonschema2pojo.util.NameHelper; + +public class GeneratorUtils { + + public static JMethod implementInterface(JDefinedClass definedClass, JFieldVar valueField) { + JMethod method = definedClass.method(JMod.PUBLIC, valueField.type(), "get"); + method.annotate(Override.class); + method.body()._return(valueField); + return method; + } + + public static JMethod getterMethod( + JDefinedClass definedClass, JFieldVar instanceField, NameHelper nameHelper, String name) { + JMethod method = + definedClass.method( + JMod.PUBLIC, + instanceField.type(), + nameHelper.getGetterName(name, instanceField.type(), null)); + method.body()._return(instanceField); + return method; + } + + private GeneratorUtils() {} +} diff --git a/generators/types/src/main/java/io/serverlessworkflow/generator/RefNameHelper.java b/generators/types/src/main/java/io/serverlessworkflow/generator/RefNameHelper.java new file mode 100644 index 00000000..6411e886 --- /dev/null +++ b/generators/types/src/main/java/io/serverlessworkflow/generator/RefNameHelper.java @@ -0,0 +1,42 @@ +/* + * 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.generator; + +import com.fasterxml.jackson.databind.JsonNode; +import com.sun.codemodel.JClassAlreadyExistsException; +import com.sun.codemodel.JDefinedClass; +import com.sun.codemodel.JPackage; +import org.jsonschema2pojo.GenerationConfig; +import org.jsonschema2pojo.util.NameHelper; + +public class RefNameHelper extends NameHelper { + + public RefNameHelper(GenerationConfig generationConfig) { + super(generationConfig); + } + + @Override + public String getUniqueClassName(String nodeName, JsonNode node, JPackage _package) { + String className = getClassName(nodeName, node, _package); + try { + JDefinedClass _class = _package._class(className); + _package.remove(_class); + return className; + } catch (JClassAlreadyExistsException ex) { + return super.getUniqueClassName(nodeName, null, _package); + } + } +} diff --git a/generators/types/src/main/java/io/serverlessworkflow/generator/UnevaluatedPropertiesRule.java b/generators/types/src/main/java/io/serverlessworkflow/generator/UnevaluatedPropertiesRule.java new file mode 100644 index 00000000..5388a503 --- /dev/null +++ b/generators/types/src/main/java/io/serverlessworkflow/generator/UnevaluatedPropertiesRule.java @@ -0,0 +1,116 @@ +/* + * 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.generator; + +import com.fasterxml.jackson.databind.JsonNode; +import com.sun.codemodel.JClassAlreadyExistsException; +import com.sun.codemodel.JDefinedClass; +import com.sun.codemodel.JExpr; +import com.sun.codemodel.JFieldVar; +import com.sun.codemodel.JMethod; +import com.sun.codemodel.JMod; +import com.sun.codemodel.JType; +import io.serverlessworkflow.annotations.Item; +import io.serverlessworkflow.annotations.ItemKey; +import io.serverlessworkflow.annotations.ItemValue; +import org.jsonschema2pojo.Schema; +import org.jsonschema2pojo.rules.AdditionalPropertiesRule; +import org.jsonschema2pojo.rules.Rule; +import org.jsonschema2pojo.rules.RuleFactory; +import org.jsonschema2pojo.util.NameHelper; + +public class UnevaluatedPropertiesRule extends AdditionalPropertiesRule + implements Rule { + + private RuleFactory ruleFactory; + + public UnevaluatedPropertiesRule(RuleFactory ruleFactory) { + super(ruleFactory); + this.ruleFactory = ruleFactory; + } + + public JDefinedClass apply( + String nodeName, JsonNode node, JsonNode parent, JDefinedClass jclass, Schema schema) { + JsonNode unevalutedNode = parent.get("unevaluatedProperties"); + if (unevalutedNode != null && unevalutedNode.isBoolean() && unevalutedNode.asBoolean() == false + || (node == null && parent.has("properties"))) { + // no additional properties allowed + return jclass; + } else if (node != null + && checkIntValue(parent, "maxProperties", 1) + && checkIntValue(parent, "minProperties", 1)) { + try { + return addKeyValueFields(jclass, node, parent, nodeName, schema); + } catch (JClassAlreadyExistsException e) { + throw new IllegalArgumentException(e); + } + } else { + return super.apply(nodeName, node, parent, jclass, schema); + } + } + + private JDefinedClass addKeyValueFields( + JDefinedClass jclass, JsonNode node, JsonNode parent, String nodeName, Schema schema) + throws JClassAlreadyExistsException { + NameHelper nameHelper = ruleFactory.getNameHelper(); + JType stringClass = jclass.owner()._ref(String.class); + JFieldVar nameField = + jclass.field(JMod.PRIVATE, stringClass, nameHelper.getPropertyName("name", null)); + JMethod nameMethod = GeneratorUtils.getterMethod(jclass, nameField, nameHelper, "name"); + JType propertyType; + if (node != null && node.size() != 0) { + String pathToAdditionalProperties; + if (schema.getId().getFragment() == null) { + pathToAdditionalProperties = "#/additionalProperties"; + } else { + pathToAdditionalProperties = "#" + schema.getId().getFragment() + "/additionalProperties"; + } + Schema additionalPropertiesSchema = + ruleFactory + .getSchemaStore() + .create( + schema, + pathToAdditionalProperties, + ruleFactory.getGenerationConfig().getRefFragmentPathDelimiters()); + propertyType = + ruleFactory + .getSchemaRule() + .apply(nodeName + "Property", node, parent, jclass, additionalPropertiesSchema); + additionalPropertiesSchema.setJavaTypeIfEmpty(propertyType); + } else { + propertyType = jclass.owner().ref(Object.class); + } + JFieldVar valueField = + jclass.field( + JMod.PRIVATE, propertyType, nameHelper.getPropertyName(propertyType.name(), null)); + JMethod valueMethod = + GeneratorUtils.getterMethod(jclass, valueField, nameHelper, propertyType.name()); + + jclass.annotate(Item.class); + nameMethod.annotate(ItemKey.class); + valueMethod.annotate(ItemValue.class); + JMethod constructor = jclass.constructor(JMod.PUBLIC); + constructor + .body() + .assign(JExpr._this().ref(nameField), constructor.param(stringClass, nameField.name())) + .assign(JExpr._this().ref(valueField), constructor.param(propertyType, valueField.name())); + return jclass; + } + + private boolean checkIntValue(JsonNode node, String propName, int value) { + return node.has(propName) && node.get(propName).asInt() == value; + } +} diff --git a/generators/types/src/main/java/io/serverlessworkflow/generator/UnreferencedFactory.java b/generators/types/src/main/java/io/serverlessworkflow/generator/UnreferencedFactory.java new file mode 100644 index 00000000..f101fb8d --- /dev/null +++ b/generators/types/src/main/java/io/serverlessworkflow/generator/UnreferencedFactory.java @@ -0,0 +1,59 @@ +/* + * 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.generator; + +import com.sun.codemodel.JClassContainer; +import com.sun.codemodel.JDefinedClass; +import com.sun.codemodel.JType; +import org.jsonschema2pojo.GenerationConfig; +import org.jsonschema2pojo.rules.Rule; +import org.jsonschema2pojo.rules.RuleFactory; +import org.jsonschema2pojo.util.NameHelper; + +public class UnreferencedFactory extends RuleFactory { + + private NameHelper refNameHelper; + + public UnreferencedFactory() { + this.refNameHelper = new RefNameHelper(getGenerationConfig()); + } + + @Override + public void setGenerationConfig(final GenerationConfig generationConfig) { + super.setGenerationConfig(generationConfig); + this.refNameHelper = new RefNameHelper(generationConfig); + } + + @Override + public Rule getSchemaRule() { + return new AllAnyOneOfSchemaRule(this); + } + + @Override + public Rule getTypeRule() { + return new EmptyObjectTypeRule(this); + } + + @Override + public Rule getAdditionalPropertiesRule() { + return new UnevaluatedPropertiesRule(this); + } + + @Override + public NameHelper getNameHelper() { + return refNameHelper; + } +} diff --git a/img/jobmonitoring.png b/img/jobmonitoring.png deleted file mode 100644 index cf53109a..00000000 Binary files a/img/jobmonitoring.png and /dev/null differ diff --git a/img/provisionorders.png b/img/provisionorders.png deleted file mode 100644 index e8fc69f5..00000000 Binary files a/img/provisionorders.png and /dev/null differ diff --git a/impl/README.md b/impl/README.md new file mode 100644 index 00000000..26655a02 --- /dev/null +++ b/impl/README.md @@ -0,0 +1,194 @@ +![Verify JAVA SDK](https://github.com/serverlessworkflow/sdk-java/workflows/Verify%20JAVA%20SDK/badge.svg) +![Deploy JAVA SDK](https://github.com/serverlessworkflow/sdk-java/workflows/Deploy%20JAVA%20SDK/badge.svg) [![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/serverlessworkflow/sdk-java) + +# Serverless Workflow Specification - Java SDK- Reference Implementation + +Welcome to Java SDK runtime reference implementation, a lightweight implementation of the Serverless Workflow specification which provides a simple, non blocking, reactive API for workflow execution. + +Although initially conceived mainly for testing purposes, it was designed to be easily expanded, so it can eventually become production ready. + +## Status + +This reference implementation is currently capable of running workflows consisting of: + + +* Tasks + * Switch + * Set + * Do + * Raise + * Listen + * Emit + * Fork + * For + * Try + * Wait + * Call + * HTTP +* Schema Validation + * Input + * Output +* Expressions + * Input + * Output + * Export + * Special keywords: runtime, workflow, task... +* Error definitions + + +## Setup + +Before getting started, ensure you have Java 17+ and Maven or Gradle installed. + +Install [Java 17](https://openjdk.org/projects/jdk/17/) +Install [Maven](https://maven.apache.org/install.html) (if using Maven) +Install [Gradle](https://gradle.org/install) (if using Gradle) + +### Dependencies + +This implementation follows a modular approach, keeping dependencies minimal: +- The core library is always required. +- Additional dependencies must be explicitly included if your workflow interacts with external services (e.g., HTTP). +This ensures you only include what you need, preventing unnecessary dependencies. + +#### Maven + +You always need to add this dependency to your pom.xml `dependencies` section: + +```xml + + io.serverlessworkflow + serverlessworkflow-impl-core + 7.0.0.Final + +``` + +And only if your workflow is using HTTP calls, you must add: + +```xml + + io.serverlessworkflow + serverlessworkflow-impl-http + 7.0.0.Final + +``` + +#### Gradle projects: + +You always need to add this dependency to your build.gradle `dependencies` section: + +```text +implementation("io.serverlessworkflow:serverlessworkflow-impl-core:7.0.0.Final") +``` + +And only if your workflow is using HTTP calls, you must add: + +```text +implementation("io.serverlessworkflow:serverlessworkflow-impl-http:7.0.0.Final") +``` + +## How to use + +The quick version is intended for impatient users who want to try something as soon as possible. + +The detailed version is more suitable for those users interested in a more thoughtful discussion of the API. + +### Quick version + +For a quick introduction, we will use a simple workflow [definition](../examples/simpleGet/src/main/resources/get.yaml) that performs a get call. +We are going to show two ways of invoking the workflow: + - blocking the thread till the get request goes through + - returning control to the caller, so the main thread continues while the get is executed + +In order to execute the workflow, blocking the thread till the HTTP request is completed, you should write + +``` java +try (WorkflowApplication appl = WorkflowApplication.builder().build()) { + logger.info( + "Workflow output is {}", + appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath("get.yaml")) + .instance(Map.of("petId", 10)) + .start() + .join()); + } +``` +You can find the complete java code [here](../examples/simpleGet/src/main/java/io/serverlessworkflow/impl/BlockingExample.java) + +In order to execute the workflow without blocking the calling thread till the HTTP request is completed, you should write + +``` java + try (WorkflowApplication appl = WorkflowApplication.builder().build()) { + appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath("get.yaml")) + .instance(Map.of("petId", 10)) + .start() + .thenAccept(node -> logger.info("Workflow output is {}", node)); + } +``` +When the HTTP request is done, both examples will print a similar output + + +```shell +Workflow output is {"id":10,"category":{"id":10,"name":"string"},"name":"doggie","photoUrls":["string"],"tags":[{"id":10,"name":"string"}],"status":"string"} +``` + +You can find the complete java code [here](../examples/simpleGet/src/main/java/io/serverlessworkflow/impl/NotBlockingExample.java) + +### Detailed version + +To discuss runtime API we are going to use a couple of workflow: +- [listen.yaml](../examples/events/src/main/listen.yaml), which waits for an event reporting a temperature greater than 38 +- [emit.yaml](../examples/events/src/main/emit.yaml), which emits events with a certain temperature, specified as workflow parameter. + +Here is a summary of what we are trying to do: + +- The listen.yaml workflow waits for an event (not-blocking). +- We send an event with a low temperature (ignored). +- We send an event with a high temperature (completes the workflow). + +The first step is to create a [WorkflowApplication](core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java) instance. An application is an abstraction that allows customization of different aspects of the workflow execution (for example, change the default `ExecutorService` for thread spawning) + +Since `WorkflowApplication` implements `Autocloseable`, we better use a **try-with-resources** block, ensuring any resource that the workflow might have used is freed when done. + +`try (WorkflowApplication appl = WorkflowApplication.builder().build())` + +Once we have the application object, we use it to parse our definition examples. To load each workflow definition, we use the `readFromClasspath` helper method defined in [WorkflowReader](api/src/main/java/io/serverlessworkflow/api/WorkflowReader.java) class. + +```java + WorkflowDefinition listenDefinition = + appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath("listen.yaml")); + WorkflowDefinition emitDefinition = + appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath("emit.yaml")); +``` + +A [WorkflowDefinition](core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java) object is immutable and, therefore, thread-safe. It is used to execute as many workflow instances as desired. + +To execute a workflow, we first create a [WorkflowInstance](core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java) object (its initial status is PENDING) and then invoke the `start` method on it (its status is changed to RUNNING). The `start` method returns a [CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html), which we use to indicate that a log message should be printed when the workflow is completed. + +```java + WorkflowInstance waitingInstance = listenDefinition.instance(Map.of()); + waitingInstance + .start() + .thenAccept(node -> logger.info("Waiting instance completed with result {}", node)); +``` + +As soon as the workflow execution reach the point where it waits for events to arrive, control is returned to the calling thread. Since the execution is not blocking, we can execute another workflow instance while the first one is waiting. + +We will send an event with a temperature that does not satisfy the criteria, so the listen instance will continue waiting. We use a regular Java `Map` to pass parameters to the workflow instance that sends the event. Note that since we want to wait till the event is published, we call `join` after `start`, telling the `CompletableFuture` to wait for workflow completion. + +```java + emitDefinition.instance(Map.of("temperature", 35)).start().join(); + ``` + + It's time to complete the waiting instance and send an event with the expected temperature. We do so by reusing `emitDefinition`. + +```java + emitDefinition.instance(Map.of("temperature", 39)).start().join(); + ``` + +After that, listen instance will be completed and we will see this log message + +```java +[pool-1-thread-1] INFO events.EventExample - Waiting instance completed with result [{"temperature":39}] +``` +The source code of the example is [here](../examples/events/src/main/java/events/EventExample.java) + diff --git a/impl/core/pom.xml b/impl/core/pom.xml new file mode 100644 index 00000000..6cc2beed --- /dev/null +++ b/impl/core/pom.xml @@ -0,0 +1,29 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-impl + 8.0.0-SNAPSHOT + + serverlessworkflow-impl-core + Serverless Workflow :: Impl :: Core + + + io.serverlessworkflow + serverlessworkflow-types + ${project.version} + + + io.cloudevents + cloudevents-core + + + org.slf4j + slf4j-api + + + com.github.f4b6a3 + ulid-creator + + + diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/DefaultExecutorServiceFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/DefaultExecutorServiceFactory.java new file mode 100644 index 00000000..1ac1f759 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/DefaultExecutorServiceFactory.java @@ -0,0 +1,39 @@ +/* + * 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.impl; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class DefaultExecutorServiceFactory implements ExecutorServiceFactory { + + private static final ExecutorServiceFactory instance = new DefaultExecutorServiceFactory(); + + public static ExecutorServiceFactory instance() { + return instance; + } + + private static class ExecutorServiceHolder { + private static ExecutorService instance = Executors.newCachedThreadPool(); + } + + @Override + public ExecutorService get() { + return ExecutorServiceHolder.instance; + } + + private DefaultExecutorServiceFactory() {} +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/ExecutorServiceFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/ExecutorServiceFactory.java new file mode 100644 index 00000000..7c211149 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/ExecutorServiceFactory.java @@ -0,0 +1,22 @@ +/* + * 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.impl; + +import java.util.concurrent.ExecutorService; +import java.util.function.Supplier; + +@FunctionalInterface +public interface ExecutorServiceFactory extends Supplier {} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/ExpressionHolder.java b/impl/core/src/main/java/io/serverlessworkflow/impl/ExpressionHolder.java new file mode 100644 index 00000000..f899f186 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/ExpressionHolder.java @@ -0,0 +1,20 @@ +/* + * 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.impl; + +import java.util.function.BiFunction; + +public interface ExpressionHolder extends BiFunction {} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/QueueWorkflowPosition.java b/impl/core/src/main/java/io/serverlessworkflow/impl/QueueWorkflowPosition.java new file mode 100644 index 00000000..5ad4934f --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/QueueWorkflowPosition.java @@ -0,0 +1,70 @@ +/* + * 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.impl; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.stream.Collectors; + +public class QueueWorkflowPosition implements WorkflowPosition { + + private Deque queue; + + QueueWorkflowPosition() { + this(new ArrayDeque<>()); + } + + private QueueWorkflowPosition(Deque list) { + this.queue = list; + } + + public QueueWorkflowPosition copy() { + return new QueueWorkflowPosition(new ArrayDeque<>(this.queue)); + } + + @Override + public WorkflowPosition addIndex(int index) { + queue.add(index); + return this; + } + + @Override + public WorkflowPosition addProperty(String prop) { + queue.add(prop); + return this; + } + + @Override + public String jsonPointer() { + return queue.stream().map(Object::toString).collect(Collectors.joining("/")); + } + + @Override + public String toString() { + return "QueueWorkflowPosition [queue=" + queue + "]"; + } + + @Override + public WorkflowPosition back() { + queue.removeLast(); + return this; + } + + @Override + public Object last() { + return queue.getLast(); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/RuntimeDescriptorFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/RuntimeDescriptorFactory.java new file mode 100644 index 00000000..2d0601fb --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/RuntimeDescriptorFactory.java @@ -0,0 +1,22 @@ +/* + * 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.impl; + +import io.serverlessworkflow.impl.expressions.RuntimeDescriptor; +import java.util.function.Supplier; + +@FunctionalInterface +public interface RuntimeDescriptorFactory extends Supplier {} diff --git a/api/src/main/java/io/serverlessworkflow/api/mapper/JsonObjectMapper.java b/impl/core/src/main/java/io/serverlessworkflow/impl/ServicePriority.java similarity index 63% rename from api/src/main/java/io/serverlessworkflow/api/mapper/JsonObjectMapper.java rename to impl/core/src/main/java/io/serverlessworkflow/impl/ServicePriority.java index 0a09fe87..74b5bdf1 100644 --- a/api/src/main/java/io/serverlessworkflow/api/mapper/JsonObjectMapper.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/ServicePriority.java @@ -1,31 +1,30 @@ /* * 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; +package io.serverlessworkflow.impl; -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; +public interface ServicePriority extends Comparable { -public class JsonObjectMapper extends BaseObjectMapper { + static final int DEFAULT_PRIORITY = 1000; - public JsonObjectMapper() { - this(null); - } + default int priority() { + return DEFAULT_PRIORITY; + } - public JsonObjectMapper(WorkflowPropertySource context) { - super(null, - context); - } -} \ No newline at end of file + @Override + default int compareTo(ServicePriority other) { + return this.priority() - other.priority(); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/StringBufferWorkflowPosition.java b/impl/core/src/main/java/io/serverlessworkflow/impl/StringBufferWorkflowPosition.java new file mode 100644 index 00000000..18aaf8e4 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/StringBufferWorkflowPosition.java @@ -0,0 +1,70 @@ +/* + * 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.impl; + +public class StringBufferWorkflowPosition implements WorkflowPosition { + + private StringBuilder sb; + + StringBufferWorkflowPosition() { + this(""); + } + + private StringBufferWorkflowPosition(String str) { + this.sb = new StringBuilder(str); + } + + public StringBufferWorkflowPosition copy() { + return new StringBufferWorkflowPosition(this.jsonPointer()); + } + + @Override + public WorkflowPosition addIndex(int index) { + sb.append('/').append(index); + return this; + } + + @Override + public WorkflowPosition addProperty(String prop) { + sb.append('/').append(prop); + return this; + } + + @Override + public String jsonPointer() { + return sb.toString(); + } + + @Override + public String toString() { + return "StringBufferWorkflowPosition [sb=" + sb + "]"; + } + + @Override + public WorkflowPosition back() { + int indexOf = sb.lastIndexOf("/"); + if (indexOf != -1) { + sb.substring(0, indexOf); + } + return this; + } + + @Override + public Object last() { + int indexOf = sb.lastIndexOf("/"); + return indexOf != -1 ? jsonPointer().substring(indexOf + 1) : ""; + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/StringFilter.java b/impl/core/src/main/java/io/serverlessworkflow/impl/StringFilter.java new file mode 100644 index 00000000..2fbec647 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/StringFilter.java @@ -0,0 +1,19 @@ +/* + * 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.impl; + +@FunctionalInterface +public interface StringFilter extends ExpressionHolder {} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/TaskContext.java b/impl/core/src/main/java/io/serverlessworkflow/impl/TaskContext.java new file mode 100644 index 00000000..91c4abab --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/TaskContext.java @@ -0,0 +1,152 @@ +/* + * 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.impl; + +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.impl.executors.TransitionInfo; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class TaskContext { + + private final WorkflowModel rawInput; + private final TaskBase task; + private final WorkflowPosition position; + private final Instant startedAt; + private final String taskName; + private final Map contextVariables; + private final Optional parentContext; + + private WorkflowModel input; + private WorkflowModel output; + private WorkflowModel rawOutput; + private Instant completedAt; + private TransitionInfo transition; + + public TaskContext( + WorkflowModel input, + WorkflowPosition position, + Optional parentContext, + String taskName, + TaskBase task) { + this(input, parentContext, taskName, task, position, Instant.now(), input, input, input); + } + + private TaskContext( + WorkflowModel rawInput, + Optional parentContext, + String taskName, + TaskBase task, + WorkflowPosition position, + Instant startedAt, + WorkflowModel input, + WorkflowModel output, + WorkflowModel rawOutput) { + this.rawInput = rawInput; + this.parentContext = parentContext; + this.taskName = taskName; + this.task = task; + this.position = position; + this.startedAt = startedAt; + this.input = input; + this.output = output; + this.rawOutput = rawOutput; + this.contextVariables = + parentContext.map(p -> new HashMap<>(p.contextVariables)).orElseGet(HashMap::new); + } + + public TaskContext copy() { + return new TaskContext( + rawInput, parentContext, taskName, task, position, startedAt, input, output, rawOutput); + } + + public void input(WorkflowModel input) { + this.input = input; + this.rawOutput = input; + this.output = input; + } + + public WorkflowModel input() { + return input; + } + + public WorkflowModel rawInput() { + return rawInput; + } + + public TaskBase task() { + return task; + } + + public TaskContext rawOutput(WorkflowModel output) { + this.rawOutput = output; + this.output = output; + return this; + } + + public WorkflowModel rawOutput() { + return rawOutput; + } + + public TaskContext output(WorkflowModel output) { + this.output = output; + return this; + } + + public WorkflowModel output() { + return output; + } + + public WorkflowPosition position() { + return position; + } + + public Map variables() { + return contextVariables; + } + + public Instant startedAt() { + return startedAt; + } + + public Optional parent() { + return parentContext; + } + + public String taskName() { + return taskName; + } + + public TaskContext completedAt(Instant instant) { + this.completedAt = instant; + return this; + } + + public Instant completedAt() { + return completedAt; + } + + public TransitionInfo transition() { + return transition; + } + + public TaskContext transition(TransitionInfo transition) { + this.transition = transition; + return this; + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java new file mode 100644 index 00000000..16063c60 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java @@ -0,0 +1,263 @@ +/* + * 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.impl; + +import com.github.f4b6a3.ulid.UlidCreator; +import io.serverlessworkflow.api.types.Document; +import io.serverlessworkflow.api.types.SchemaInline; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.events.EventConsumer; +import io.serverlessworkflow.impl.events.EventPublisher; +import io.serverlessworkflow.impl.events.InMemoryEvents; +import io.serverlessworkflow.impl.executors.DefaultTaskExecutorFactory; +import io.serverlessworkflow.impl.executors.TaskExecutorFactory; +import io.serverlessworkflow.impl.expressions.ExpressionFactory; +import io.serverlessworkflow.impl.expressions.RuntimeDescriptor; +import io.serverlessworkflow.impl.resources.DefaultResourceLoaderFactory; +import io.serverlessworkflow.impl.resources.ResourceLoaderFactory; +import io.serverlessworkflow.impl.resources.StaticResource; +import io.serverlessworkflow.impl.schema.SchemaValidator; +import io.serverlessworkflow.impl.schema.SchemaValidatorFactory; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class WorkflowApplication implements AutoCloseable { + + private final TaskExecutorFactory taskFactory; + private final ExpressionFactory exprFactory; + private final ResourceLoaderFactory resourceLoaderFactory; + private final SchemaValidatorFactory schemaValidatorFactory; + private final WorkflowIdFactory idFactory; + private final Collection listeners; + private final Map definitions; + private final WorkflowPositionFactory positionFactory; + private final ExecutorServiceFactory executorFactory; + private final RuntimeDescriptorFactory runtimeDescriptorFactory; + private final EventConsumer eventConsumer; + private final EventPublisher eventPublisher; + + private WorkflowApplication(Builder builder) { + this.taskFactory = builder.taskFactory; + this.exprFactory = builder.exprFactory; + this.resourceLoaderFactory = builder.resourceLoaderFactory; + this.schemaValidatorFactory = builder.schemaValidatorFactory; + this.positionFactory = builder.positionFactory; + this.idFactory = builder.idFactory; + this.runtimeDescriptorFactory = builder.descriptorFactory; + this.executorFactory = builder.executorFactory; + this.listeners = builder.listeners != null ? builder.listeners : Collections.emptySet(); + this.definitions = new ConcurrentHashMap<>(); + this.eventConsumer = builder.eventConsumer; + this.eventPublisher = builder.eventPublisher; + } + + public TaskExecutorFactory taskFactory() { + return taskFactory; + } + + public static Builder builder() { + return new Builder(); + } + + public ExpressionFactory expressionFactory() { + return exprFactory; + } + + public SchemaValidatorFactory validatorFactory() { + return schemaValidatorFactory; + } + + public ResourceLoaderFactory resourceLoaderFactory() { + return resourceLoaderFactory; + } + + public Collection listeners() { + return listeners; + } + + public EventPublisher eventPublisher() { + return eventPublisher; + } + + public WorkflowIdFactory idFactory() { + return idFactory; + } + + public static class Builder { + + private static final class EmptySchemaValidatorHolder { + private static final SchemaValidatorFactory instance = + new SchemaValidatorFactory() { + private final SchemaValidator NoValidation = + new SchemaValidator() { + @Override + public void validate(WorkflowModel node) {} + }; + + @Override + public SchemaValidator getValidator(StaticResource resource) { + return NoValidation; + } + + @Override + public SchemaValidator getValidator(SchemaInline inline) { + return NoValidation; + } + }; + } + + private TaskExecutorFactory taskFactory; + private ExpressionFactory exprFactory; + private Collection listeners; + private ResourceLoaderFactory resourceLoaderFactory = DefaultResourceLoaderFactory.get(); + private SchemaValidatorFactory schemaValidatorFactory; + private WorkflowPositionFactory positionFactory = () -> new QueueWorkflowPosition(); + private WorkflowIdFactory idFactory = () -> UlidCreator.getMonotonicUlid().toString(); + private ExecutorServiceFactory executorFactory = () -> Executors.newCachedThreadPool(); + private EventConsumer eventConsumer = InMemoryEvents.get(); + private EventPublisher eventPublisher = InMemoryEvents.get(); + private RuntimeDescriptorFactory descriptorFactory = + () -> new RuntimeDescriptor("reference impl", "1.0.0_alpha", Collections.emptyMap()); + + private Builder() {} + + public Builder withListener(WorkflowExecutionListener listener) { + if (listeners == null) { + listeners = new HashSet<>(); + } + listeners.add(listener); + return this; + } + + public Builder withTaskExecutorFactory(TaskExecutorFactory factory) { + this.taskFactory = factory; + return this; + } + + public Builder withExpressionFactory(ExpressionFactory factory) { + this.exprFactory = factory; + return this; + } + + public Builder withResourceLoaderFactory(ResourceLoaderFactory resourceLoader) { + this.resourceLoaderFactory = resourceLoader; + return this; + } + + public Builder withExecutorFactory(ExecutorServiceFactory executorFactory) { + this.executorFactory = executorFactory; + return this; + } + + public Builder withPositionFactory(WorkflowPositionFactory positionFactory) { + this.positionFactory = positionFactory; + return this; + } + + public Builder withSchemaValidatorFactory(SchemaValidatorFactory factory) { + this.schemaValidatorFactory = factory; + return this; + } + + public Builder withIdFactory(WorkflowIdFactory factory) { + this.idFactory = factory; + return this; + } + + public Builder withDescriptorFactory(RuntimeDescriptorFactory factory) { + this.descriptorFactory = factory; + return this; + } + + public Builder withEventConsumer(EventConsumer eventConsumer) { + this.eventConsumer = eventConsumer; + return this; + } + + public Builder withEventPublisher(EventPublisher eventPublisher) { + this.eventPublisher = eventPublisher; + return this; + } + + public WorkflowApplication build() { + if (exprFactory == null) { + exprFactory = + ServiceLoader.load(ExpressionFactory.class) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Expression factory is required")); + } + if (schemaValidatorFactory == null) { + schemaValidatorFactory = + ServiceLoader.load(SchemaValidatorFactory.class) + .findFirst() + .orElseGet(() -> EmptySchemaValidatorHolder.instance); + } + if (taskFactory == null) { + taskFactory = + ServiceLoader.load(TaskExecutorFactory.class) + .findFirst() + .orElseGet(() -> DefaultTaskExecutorFactory.get()); + } + return new WorkflowApplication(this); + } + } + + private static record WorkflowId(String namespace, String name, String version) { + static WorkflowId of(Document document) { + return new WorkflowId(document.getNamespace(), document.getName(), document.getVersion()); + } + } + + public WorkflowDefinition workflowDefinition(Workflow workflow) { + return definitions.computeIfAbsent( + WorkflowId.of(workflow.getDocument()), k -> WorkflowDefinition.of(this, workflow)); + } + + @Override + public void close() { + for (WorkflowDefinition definition : definitions.values()) { + definition.close(); + } + definitions.clear(); + } + + public WorkflowPositionFactory positionFactory() { + return positionFactory; + } + + public WorkflowModelFactory modelFactory() { + return exprFactory.modelFactory(); + } + + public RuntimeDescriptorFactory runtimeDescriptorFactory() { + return runtimeDescriptorFactory; + } + + @SuppressWarnings("rawtypes") + public EventConsumer eventConsumer() { + return eventConsumer; + } + + public ExecutorService executorService() { + return executorFactory.get(); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowContext.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowContext.java new file mode 100644 index 00000000..6960ca66 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowContext.java @@ -0,0 +1,43 @@ +/* + * 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.impl; + +public class WorkflowContext { + private final WorkflowDefinition definition; + private final WorkflowInstance instance; + private WorkflowModel context; + + WorkflowContext(WorkflowDefinition definition, WorkflowInstance instance) { + this.definition = definition; + this.instance = instance; + } + + public WorkflowInstance instance() { + return instance; + } + + public WorkflowModel context() { + return context; + } + + public void context(WorkflowModel context) { + this.context = context; + } + + public WorkflowDefinition definition() { + return definition; + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java new file mode 100644 index 00000000..404ecf07 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java @@ -0,0 +1,119 @@ +/* + * 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.impl; + +import static io.serverlessworkflow.impl.WorkflowUtils.*; + +import io.serverlessworkflow.api.types.Input; +import io.serverlessworkflow.api.types.Output; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.executors.TaskExecutor; +import io.serverlessworkflow.impl.executors.TaskExecutorHelper; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import io.serverlessworkflow.impl.schema.SchemaValidator; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Optional; + +public class WorkflowDefinition implements AutoCloseable { + + private final Workflow workflow; + private Optional inputSchemaValidator = Optional.empty(); + private Optional outputSchemaValidator = Optional.empty(); + private Optional inputFilter = Optional.empty(); + private Optional outputFilter = Optional.empty(); + private final WorkflowApplication application; + private final TaskExecutor taskExecutor; + + private WorkflowDefinition( + WorkflowApplication application, Workflow workflow, ResourceLoader resourceLoader) { + this.workflow = workflow; + this.application = application; + if (workflow.getInput() != null) { + Input input = workflow.getInput(); + this.inputSchemaValidator = + getSchemaValidator(application.validatorFactory(), resourceLoader, input.getSchema()); + this.inputFilter = buildWorkflowFilter(application, input.getFrom()); + } + if (workflow.getOutput() != null) { + Output output = workflow.getOutput(); + this.outputSchemaValidator = + getSchemaValidator(application.validatorFactory(), resourceLoader, output.getSchema()); + this.outputFilter = buildWorkflowFilter(application, output.getAs()); + } + this.taskExecutor = + TaskExecutorHelper.createExecutorList( + application.positionFactory().get(), + workflow.getDo(), + workflow, + application, + resourceLoader); + } + + static WorkflowDefinition of(WorkflowApplication application, Workflow workflow) { + return of(application, workflow, null); + } + + static WorkflowDefinition of(WorkflowApplication application, Workflow workflow, Path path) { + return new WorkflowDefinition( + application, workflow, application.resourceLoaderFactory().getResourceLoader(path)); + } + + public WorkflowInstance instance(Object input) { + return new WorkflowInstance(this, application.modelFactory().fromAny(input)); + } + + Optional inputSchemaValidator() { + return inputSchemaValidator; + } + + TaskExecutor startTask() { + return taskExecutor; + } + + Optional inputFilter() { + return inputFilter; + } + + public Workflow workflow() { + return workflow; + } + + public Collection listeners() { + return application.listeners(); + } + + Optional outputFilter() { + return outputFilter; + } + + Optional outputSchemaValidator() { + return outputSchemaValidator; + } + + public RuntimeDescriptorFactory runtimeDescriptorFactory() { + return application.runtimeDescriptorFactory(); + } + + public WorkflowApplication application() { + return application; + } + + @Override + public void close() { + // TODO close resourcers hold for uncompleted process instances, if any + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowError.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowError.java new file mode 100644 index 00000000..b72cdbb0 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowError.java @@ -0,0 +1,73 @@ +/* + * 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.impl; + +public record WorkflowError( + String type, int status, String instance, String title, String details) { + + private static final String ERROR_FORMAT = "https://serverlessworkflow.io/spec/1.0.0/errors/%s"; + public static final String RUNTIME_TYPE = String.format(ERROR_FORMAT, "runtime"); + public static final String COMM_TYPE = String.format(ERROR_FORMAT, "communication"); + + public static Builder error(String type, int status) { + return new Builder(type, status); + } + + public static Builder communication(int status, TaskContext context, Exception ex) { + return new Builder(COMM_TYPE, status) + .instance(context.position().jsonPointer()) + .title(ex.getMessage()); + } + + public static Builder runtime(int status, TaskContext context, Exception ex) { + return new Builder(RUNTIME_TYPE, status) + .instance(context.position().jsonPointer()) + .title(ex.getMessage()); + } + + public static class Builder { + + private final String type; + private int status; + private String instance; + private String title; + private String details; + + private Builder(String type, int status) { + this.type = type; + this.status = status; + } + + public Builder instance(String instance) { + this.instance = instance; + return this; + } + + public Builder title(String title) { + this.title = title; + return this; + } + + public Builder details(String details) { + this.details = details; + return this; + } + + public WorkflowError build() { + return new WorkflowError(type, status, instance, title, details); + } + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowException.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowException.java new file mode 100644 index 00000000..685fc077 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowException.java @@ -0,0 +1,36 @@ +/* + * 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.impl; + +public class WorkflowException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + private final WorkflowError worflowError; + + public WorkflowException(WorkflowError error) { + this(error, null); + } + + public WorkflowException(WorkflowError error, Throwable cause) { + super(error.toString(), cause); + this.worflowError = error; + } + + public WorkflowError getWorflowError() { + return worflowError; + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowExecutionListener.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowExecutionListener.java new file mode 100644 index 00000000..c121bb41 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowExecutionListener.java @@ -0,0 +1,25 @@ +/* + * 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.impl; + +import io.serverlessworkflow.api.types.TaskBase; + +public interface WorkflowExecutionListener { + + void onTaskStarted(WorkflowPosition currentPos, TaskBase task); + + void onTaskEnded(WorkflowPosition currentPos, TaskBase task); +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowFilter.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowFilter.java new file mode 100644 index 00000000..04c34d29 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowFilter.java @@ -0,0 +1,21 @@ +/* + * 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.impl; + +@FunctionalInterface +public interface WorkflowFilter { + WorkflowModel apply(WorkflowContext workflow, TaskContext task, WorkflowModel node); +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowIdFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowIdFactory.java new file mode 100644 index 00000000..12b0f7c6 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowIdFactory.java @@ -0,0 +1,21 @@ +/* + * 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.impl; + +import java.util.function.Supplier; + +@FunctionalInterface +public interface WorkflowIdFactory extends Supplier {} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java new file mode 100644 index 00000000..88269082 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowInstance.java @@ -0,0 +1,110 @@ +/* + * 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.impl; + +import io.serverlessworkflow.impl.executors.TaskExecutorHelper; +import java.time.Instant; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; + +public class WorkflowInstance { + private final AtomicReference status; + private final String id; + private final WorkflowModel input; + + private WorkflowContext workflowContext; + private WorkflowDefinition definition; + private Instant startedAt; + private Instant completedAt; + private volatile WorkflowModel output; + private CompletableFuture completableFuture; + + WorkflowInstance(WorkflowDefinition definition, WorkflowModel input) { + this.id = definition.application().idFactory().get(); + this.input = input; + this.definition = definition; + this.status = new AtomicReference<>(WorkflowStatus.PENDING); + definition.inputSchemaValidator().ifPresent(v -> v.validate(input)); + } + + public CompletableFuture start() { + this.startedAt = Instant.now(); + this.workflowContext = new WorkflowContext(definition, this); + this.status.set(WorkflowStatus.RUNNING); + this.completableFuture = + TaskExecutorHelper.processTaskList( + definition.startTask(), + workflowContext, + Optional.empty(), + definition + .inputFilter() + .map(f -> f.apply(workflowContext, null, input)) + .orElse(input)) + .thenApply(this::whenCompleted); + return completableFuture; + } + + private WorkflowModel whenCompleted(WorkflowModel node) { + output = + workflowContext + .definition() + .outputFilter() + .map(f -> f.apply(workflowContext, null, node)) + .orElse(node); + workflowContext.definition().outputSchemaValidator().ifPresent(v -> v.validate(output)); + status.compareAndSet(WorkflowStatus.RUNNING, WorkflowStatus.COMPLETED); + completedAt = Instant.now(); + return output; + } + + public String id() { + return id; + } + + public Instant startedAt() { + return startedAt; + } + + public Instant completedAt() { + return completedAt; + } + + public WorkflowModel input() { + return input; + } + + public WorkflowStatus status() { + return status.get(); + } + + public void status(WorkflowStatus state) { + this.status.set(state); + } + + public WorkflowModel output() { + return output; + } + + public T outputAs(Class clazz) { + return output + .as(clazz) + .orElseThrow( + () -> + new IllegalArgumentException( + "Output " + output + " cannot be converted to class " + clazz)); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModel.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModel.java new file mode 100644 index 00000000..dc45896f --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModel.java @@ -0,0 +1,50 @@ +/* + * 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.impl; + +import io.cloudevents.CloudEventData; +import java.time.OffsetDateTime; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiConsumer; + +public interface WorkflowModel { + + void forEach(BiConsumer consumer); + + Optional asBoolean(); + + Collection asCollection(); + + Optional asText(); + + Optional asDate(); + + Optional asNumber(); + + Optional asCloudEventData(); + + Optional> asMap(); + + Object asJavaObject(); + + Object asIs(); + + Class objectClass(); + + Optional as(Class clazz); +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModelCollection.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModelCollection.java new file mode 100644 index 00000000..09f1dd75 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModelCollection.java @@ -0,0 +1,61 @@ +/* + * 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.impl; + +import io.cloudevents.CloudEventData; +import java.time.OffsetDateTime; +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiConsumer; + +public interface WorkflowModelCollection extends WorkflowModel, Collection { + + default void forEach(BiConsumer consumer) {} + + @Override + default Collection asCollection() { + return this; + } + + @Override + default Optional asBoolean() { + return Optional.empty(); + } + + @Override + default Optional asText() { + return Optional.empty(); + } + + @Override + default Optional asNumber() { + return Optional.empty(); + } + + @Override + public default Optional asDate() { + return Optional.empty(); + } + + default Optional asCloudEventData() { + return Optional.empty(); + } + + default Optional> asMap() { + return Optional.empty(); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModelFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModelFactory.java new file mode 100644 index 00000000..25564bc0 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModelFactory.java @@ -0,0 +1,73 @@ +/* + * 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.impl; + +import io.cloudevents.CloudEvent; +import io.cloudevents.CloudEventData; +import java.time.OffsetDateTime; +import java.util.Map; + +public interface WorkflowModelFactory { + + WorkflowModel combine(Map workflowVariables); + + WorkflowModelCollection createCollection(); + + WorkflowModel from(boolean value); + + WorkflowModel from(Number value); + + WorkflowModel from(String value); + + WorkflowModel from(CloudEvent ce); + + WorkflowModel from(CloudEventData ce); + + WorkflowModel from(OffsetDateTime value); + + WorkflowModel from(Map map); + + WorkflowModel fromNull(); + + default WorkflowModel fromOther(Object obj) { + throw new IllegalArgumentException( + "Unsupported conversion for object " + obj + " of type" + obj.getClass()); + } + + default WorkflowModel fromAny(Object obj) { + if (obj == null) { + return fromNull(); + } else if (obj instanceof Boolean value) { + return from(value); + } else if (obj instanceof Number value) { + return from(value); + } else if (obj instanceof String value) { + return from(value); + } else if (obj instanceof CloudEvent value) { + return from(value); + } else if (obj instanceof CloudEventData value) { + return from(value); + } else if (obj instanceof OffsetDateTime value) { + return from(value); + } else if (obj instanceof Map) { + return from((Map) obj); + } else if (obj instanceof WorkflowModel model) { + return model; + } else { + return fromOther(obj); + } + } +} diff --git a/api/src/main/java/io/serverlessworkflow/api/interfaces/WorkflowDiagram.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowPosition.java similarity index 63% rename from api/src/main/java/io/serverlessworkflow/api/interfaces/WorkflowDiagram.java rename to impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowPosition.java index a01e3f10..1c416100 100644 --- a/api/src/main/java/io/serverlessworkflow/api/interfaces/WorkflowDiagram.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowPosition.java @@ -1,31 +1,31 @@ /* * 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.interfaces; +package io.serverlessworkflow.impl; + +public interface WorkflowPosition { -import io.serverlessworkflow.api.Workflow; + String jsonPointer(); -import java.io.File; + WorkflowPosition addProperty(String prop); -public interface WorkflowDiagram { - WorkflowDiagram setWorkflow(Workflow workflow); + WorkflowPosition addIndex(int index); - WorkflowDiagram setSource(String source); + WorkflowPosition back(); - String getSvgDiagram() throws Exception; + WorkflowPosition copy(); - WorkflowDiagram showLegend(boolean showLegend); + Object last(); } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowPositionFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowPositionFactory.java new file mode 100644 index 00000000..60fa5d6a --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowPositionFactory.java @@ -0,0 +1,21 @@ +/* + * 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.impl; + +import java.util.function.Supplier; + +@FunctionalInterface +public interface WorkflowPositionFactory extends Supplier {} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowStatus.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowStatus.java new file mode 100644 index 00000000..bc657839 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowStatus.java @@ -0,0 +1,25 @@ +/* + * 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.impl; + +public enum WorkflowStatus { + PENDING, + RUNNING, + WAITING, + COMPLETED, + FAULTED, + CANCELLED +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java new file mode 100644 index 00000000..4a343e3c --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java @@ -0,0 +1,130 @@ +/* + * 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.impl; + +import io.serverlessworkflow.api.types.ExportAs; +import io.serverlessworkflow.api.types.InputFrom; +import io.serverlessworkflow.api.types.OutputAs; +import io.serverlessworkflow.api.types.SchemaUnion; +import io.serverlessworkflow.api.types.UriTemplate; +import io.serverlessworkflow.impl.expressions.ExpressionUtils; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import io.serverlessworkflow.impl.schema.SchemaValidator; +import io.serverlessworkflow.impl.schema.SchemaValidatorFactory; +import java.net.URI; +import java.util.Optional; +import java.util.function.Function; + +public class WorkflowUtils { + + private WorkflowUtils() {} + + public static Optional getSchemaValidator( + SchemaValidatorFactory validatorFactory, ResourceLoader resourceLoader, SchemaUnion schema) { + if (schema != null) { + + if (schema.getSchemaInline() != null) { + return Optional.of(validatorFactory.getValidator(schema.getSchemaInline())); + } else if (schema.getSchemaExternal() != null) { + return Optional.of( + validatorFactory.getValidator( + resourceLoader.loadStatic(schema.getSchemaExternal().getResource()))); + } + } + return Optional.empty(); + } + + public static Optional buildWorkflowFilter( + WorkflowApplication app, InputFrom from) { + return from != null + ? Optional.of(buildWorkflowFilter(app, from.getString(), from.getObject())) + : Optional.empty(); + } + + public static Optional buildWorkflowFilter(WorkflowApplication app, OutputAs as) { + return as != null + ? Optional.of(buildWorkflowFilter(app, as.getString(), as.getObject())) + : Optional.empty(); + } + + public static ExpressionHolder buildExpressionHolder( + WorkflowApplication app, String expression, T literal, Function converter) { + return expression != null + ? buildExpressionHolder(buildWorkflowFilter(app, expression), converter) + : buildExpressionHolder(literal); + } + + private static ExpressionHolder buildExpressionHolder( + WorkflowFilter filter, Function converter) { + return (w, t) -> converter.apply(filter.apply(w, t, t.input())); + } + + private static ExpressionHolder buildExpressionHolder(T literal) { + return (w, t) -> literal; + } + + public static Optional buildWorkflowFilter(WorkflowApplication app, ExportAs as) { + return as != null + ? Optional.of(buildWorkflowFilter(app, as.getString(), as.getObject())) + : Optional.empty(); + } + + public static StringFilter buildStringFilter( + WorkflowApplication app, String expression, String literal) { + return expression != null ? toString(buildWorkflowFilter(app, expression)) : toString(literal); + } + + public static StringFilter buildStringFilter(WorkflowApplication app, String str) { + return ExpressionUtils.isExpr(str) ? toString(buildWorkflowFilter(app, str)) : toString(str); + } + + private static StringFilter toString(WorkflowFilter filter) { + return (w, t) -> + filter + .apply(w, t, t.input()) + .asText() + .orElseThrow(() -> new IllegalArgumentException("Result is not an string")); + } + + private static StringFilter toString(String literal) { + return (w, t) -> literal; + } + + public static WorkflowFilter buildWorkflowFilter( + WorkflowApplication app, String str, Object object) { + return app.expressionFactory().buildFilter(str, object); + } + + public static WorkflowFilter buildWorkflowFilter(WorkflowApplication app, String str) { + return app.expressionFactory().buildFilter(str, null); + } + + public static Optional optionalFilter(WorkflowApplication app, String str) { + return str != null ? Optional.of(buildWorkflowFilter(app, str)) : Optional.empty(); + } + + public static Optional optionalFilter( + WorkflowApplication app, Object obj, String str) { + return str != null || obj != null + ? Optional.of(buildWorkflowFilter(app, str, obj)) + : Optional.empty(); + } + + public static String toString(UriTemplate template) { + URI uri = template.getLiteralUri(); + return uri != null ? uri.toString() : template.getLiteralUriTemplate(); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/events/AbstractTypeConsumer.java b/impl/core/src/main/java/io/serverlessworkflow/impl/events/AbstractTypeConsumer.java new file mode 100644 index 00000000..d4a613d2 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/events/AbstractTypeConsumer.java @@ -0,0 +1,136 @@ +/* + * 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.impl.events; + +import io.cloudevents.CloudEvent; +import io.serverlessworkflow.api.types.EventFilter; +import io.serverlessworkflow.api.types.EventProperties; +import io.serverlessworkflow.impl.WorkflowApplication; +import java.util.AbstractCollection; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class AbstractTypeConsumer + implements EventConsumer { + + private static final Logger logger = LoggerFactory.getLogger(AbstractTypeConsumer.class); + + protected abstract void registerToAll(Consumer consumer); + + protected abstract void unregisterFromAll(); + + protected abstract void register(String topicName, Consumer consumer); + + protected abstract void unregister(String topicName); + + private Map registrations = new ConcurrentHashMap<>(); + + @Override + public TypeEventRegistrationBuilder listen( + EventFilter register, WorkflowApplication application) { + EventProperties properties = register.getWith(); + String type = properties.getType(); + return new TypeEventRegistrationBuilder( + type, new DefaultCloudEventPredicate(properties, application)); + } + + @Override + public Collection listenToAll(WorkflowApplication application) { + return List.of(new TypeEventRegistrationBuilder(null, null)); + } + + private static class CloudEventConsumer extends AbstractCollection + implements Consumer { + private Collection registrations = new CopyOnWriteArrayList<>(); + + @Override + public void accept(CloudEvent ce) { + logger.debug("Received cloud event {}", ce); + for (TypeEventRegistration registration : registrations) { + if (registration.predicate().test(ce)) { + registration.consumer().accept(ce); + } + } + } + + @Override + public boolean add(TypeEventRegistration registration) { + return registrations.add(registration); + } + + @Override + public boolean remove(Object registration) { + return registrations.remove(registration); + } + + @Override + public Iterator iterator() { + return registrations.iterator(); + } + + @Override + public int size() { + return registrations.size(); + } + } + + public TypeEventRegistration register( + TypeEventRegistrationBuilder builder, Consumer ce) { + if (builder.type() == null) { + registerToAll(ce); + return new TypeEventRegistration(null, ce, null); + } else { + TypeEventRegistration registration = + new TypeEventRegistration(builder.type(), ce, builder.cePredicate()); + registrations + .computeIfAbsent( + registration.type(), + k -> { + CloudEventConsumer consumer = new CloudEventConsumer(); + register(k, consumer); + return consumer; + }) + .add(registration); + return registration; + } + } + + @Override + public void unregister(TypeEventRegistration registration) { + if (registration.type() == null) { + unregisterFromAll(); + } else { + registrations.computeIfPresent( + registration.type(), + (k, v) -> { + v.remove(registration); + if (v.isEmpty()) { + unregister(registration.type()); + return null; + } else { + return v; + } + }); + } + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/events/CloudEventAttrPredicate.java b/impl/core/src/main/java/io/serverlessworkflow/impl/events/CloudEventAttrPredicate.java new file mode 100644 index 00000000..6029d484 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/events/CloudEventAttrPredicate.java @@ -0,0 +1,21 @@ +/* + * 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.impl.events; + +@FunctionalInterface +public interface CloudEventAttrPredicate { + boolean test(T value); +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/events/CloudEventPredicate.java b/impl/core/src/main/java/io/serverlessworkflow/impl/events/CloudEventPredicate.java new file mode 100644 index 00000000..a790e371 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/events/CloudEventPredicate.java @@ -0,0 +1,22 @@ +/* + * 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.impl.events; + +import io.cloudevents.CloudEvent; + +public interface CloudEventPredicate { + boolean test(CloudEvent event); +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/events/CloudEventUtils.java b/impl/core/src/main/java/io/serverlessworkflow/impl/events/CloudEventUtils.java new file mode 100644 index 00000000..f2857ab0 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/events/CloudEventUtils.java @@ -0,0 +1,40 @@ +/* + * 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.impl.events; + +import io.cloudevents.CloudEvent; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; + +public class CloudEventUtils { + + private CloudEventUtils() {} + + public static OffsetDateTime toOffset(Date date) { + return date.toInstant().atOffset(ZoneOffset.UTC); + } + + public static Map extensions(CloudEvent event) { + Map result = new LinkedHashMap<>(); + for (String name : event.getExtensionNames()) { + result.put(name, event.getExtension(name)); + } + return result; + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/events/DefaultCloudEventPredicate.java b/impl/core/src/main/java/io/serverlessworkflow/impl/events/DefaultCloudEventPredicate.java new file mode 100644 index 00000000..ee319727 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/events/DefaultCloudEventPredicate.java @@ -0,0 +1,168 @@ +/* + * 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.impl.events; + +import io.cloudevents.CloudEvent; +import io.cloudevents.CloudEventData; +import io.serverlessworkflow.api.types.EventData; +import io.serverlessworkflow.api.types.EventDataschema; +import io.serverlessworkflow.api.types.EventProperties; +import io.serverlessworkflow.api.types.EventSource; +import io.serverlessworkflow.api.types.EventTime; +import io.serverlessworkflow.api.types.UriTemplate; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowModelFactory; +import io.serverlessworkflow.impl.WorkflowUtils; +import io.serverlessworkflow.impl.expressions.Expression; +import java.net.URI; +import java.time.OffsetDateTime; +import java.util.Map; +import java.util.Objects; + +public class DefaultCloudEventPredicate implements CloudEventPredicate { + + private final CloudEventAttrPredicate idFilter; + private final CloudEventAttrPredicate sourceFilter; + private final CloudEventAttrPredicate subjectFilter; + private final CloudEventAttrPredicate contentTypeFilter; + private final CloudEventAttrPredicate typeFilter; + private final CloudEventAttrPredicate dataSchemaFilter; + private final CloudEventAttrPredicate timeFilter; + private final CloudEventAttrPredicate dataFilter; + private final CloudEventAttrPredicate> additionalFilter; + + private static final CloudEventAttrPredicate isTrue() { + return x -> true; + } + + public DefaultCloudEventPredicate(EventProperties properties, WorkflowApplication app) { + idFilter = stringFilter(properties.getId()); + subjectFilter = stringFilter(properties.getSubject()); + typeFilter = stringFilter(properties.getType()); + contentTypeFilter = stringFilter(properties.getDatacontenttype()); + sourceFilter = sourceFilter(properties.getSource(), app); + dataSchemaFilter = dataSchemaFilter(properties.getDataschema(), app); + timeFilter = offsetTimeFilter(properties.getTime(), app); + dataFilter = dataFilter(properties.getData(), app); + additionalFilter = additionalFilter(properties.getAdditionalProperties(), app); + } + + private CloudEventAttrPredicate> additionalFilter( + Map additionalProperties, WorkflowApplication app) { + return additionalProperties != null && !additionalProperties.isEmpty() + ? fromMap( + app.modelFactory(), WorkflowUtils.buildWorkflowFilter(app, null, additionalProperties)) + : isTrue(); + } + + private CloudEventAttrPredicate fromCloudEvent( + WorkflowModelFactory workflowModelFactory, WorkflowFilter filter) { + return d -> filter.apply(null, null, workflowModelFactory.from(d)).asBoolean().orElse(false); + } + + private CloudEventAttrPredicate> fromMap( + WorkflowModelFactory workflowModelFactory, WorkflowFilter filter) { + return d -> filter.apply(null, null, workflowModelFactory.from(d)).asBoolean().orElse(false); + } + + private CloudEventAttrPredicate dataFilter( + EventData data, WorkflowApplication app) { + return data != null + ? fromCloudEvent( + app.modelFactory(), + WorkflowUtils.buildWorkflowFilter(app, data.getRuntimeExpression(), data.getObject())) + : isTrue(); + } + + private CloudEventAttrPredicate offsetTimeFilter( + EventTime time, WorkflowApplication app) { + if (time != null) { + if (time.getRuntimeExpression() != null) { + final Expression expr = + app.expressionFactory().buildExpression(time.getRuntimeExpression()); + return s -> evalExpr(app.modelFactory(), expr, s); + } else if (time.getLiteralTime() != null) { + return s -> Objects.equals(s, CloudEventUtils.toOffset(time.getLiteralTime())); + } + } + return isTrue(); + } + + private CloudEventAttrPredicate dataSchemaFilter( + EventDataschema dataSchema, WorkflowApplication app) { + if (dataSchema != null) { + if (dataSchema.getExpressionDataSchema() != null) { + final Expression expr = + app.expressionFactory().buildExpression(dataSchema.getExpressionDataSchema()); + return s -> evalExpr(app.modelFactory(), expr, toString(s)); + } else if (dataSchema.getLiteralDataSchema() != null) { + return templateFilter(dataSchema.getLiteralDataSchema()); + } + } + return isTrue(); + } + + private CloudEventAttrPredicate stringFilter(String str) { + return str == null ? isTrue() : x -> x.equals(str); + } + + private CloudEventAttrPredicate sourceFilter(EventSource source, WorkflowApplication app) { + if (source != null) { + if (source.getRuntimeExpression() != null) { + final Expression expr = + app.expressionFactory().buildExpression(source.getRuntimeExpression()); + return s -> evalExpr(app.modelFactory(), expr, toString(s)); + } else if (source.getUriTemplate() != null) { + return templateFilter(source.getUriTemplate()); + } + } + return isTrue(); + } + + private CloudEventAttrPredicate templateFilter(UriTemplate template) { + if (template.getLiteralUri() != null) { + return u -> Objects.equals(u, template.getLiteralUri()); + } + throw new UnsupportedOperationException("Template not supported here yet"); + } + + private String toString(T uri) { + return uri != null ? uri.toString() : null; + } + + private boolean evalExpr(WorkflowModelFactory modelFactory, Expression expr, String value) { + return expr.eval(null, null, modelFactory.from(value)).asBoolean().orElse(false); + } + + private boolean evalExpr( + WorkflowModelFactory modelFactory, Expression expr, OffsetDateTime value) { + return expr.eval(null, null, modelFactory.from(value)).asBoolean().orElse(false); + } + + @Override + public boolean test(CloudEvent event) { + return idFilter.test(event.getId()) + && sourceFilter.test(event.getSource()) + && subjectFilter.test(event.getSubject()) + && contentTypeFilter.test(event.getDataContentType()) + && typeFilter.test(event.getType()) + && dataSchemaFilter.test(event.getDataSchema()) + && timeFilter.test(event.getTime()) + && dataFilter.test(event.getData()) + && additionalFilter.test(CloudEventUtils.extensions(event)); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/events/EventConsumer.java b/impl/core/src/main/java/io/serverlessworkflow/impl/events/EventConsumer.java new file mode 100644 index 00000000..00c1619e --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/events/EventConsumer.java @@ -0,0 +1,33 @@ +/* + * 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.impl.events; + +import io.cloudevents.CloudEvent; +import io.serverlessworkflow.api.types.EventFilter; +import io.serverlessworkflow.impl.WorkflowApplication; +import java.util.Collection; +import java.util.function.Consumer; + +public interface EventConsumer { + + V listen(EventFilter filter, WorkflowApplication workflowApplication); + + Collection listenToAll(WorkflowApplication workflowApplication); + + T register(V builder, Consumer consumer); + + void unregister(T register); +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/events/EventPublisher.java b/impl/core/src/main/java/io/serverlessworkflow/impl/events/EventPublisher.java new file mode 100644 index 00000000..08cc121d --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/events/EventPublisher.java @@ -0,0 +1,23 @@ +/* + * 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.impl.events; + +import io.cloudevents.CloudEvent; +import java.util.concurrent.CompletableFuture; + +public interface EventPublisher { + CompletableFuture publish(CloudEvent event); +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/events/EventRegistration.java b/impl/core/src/main/java/io/serverlessworkflow/impl/events/EventRegistration.java new file mode 100644 index 00000000..923647d5 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/events/EventRegistration.java @@ -0,0 +1,18 @@ +/* + * 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.impl.events; + +public interface EventRegistration {} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/events/EventRegistrationBuilder.java b/impl/core/src/main/java/io/serverlessworkflow/impl/events/EventRegistrationBuilder.java new file mode 100644 index 00000000..e81723ff --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/events/EventRegistrationBuilder.java @@ -0,0 +1,18 @@ +/* + * 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.impl.events; + +public interface EventRegistrationBuilder {} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/events/InMemoryEvents.java b/impl/core/src/main/java/io/serverlessworkflow/impl/events/InMemoryEvents.java new file mode 100644 index 00000000..edd2dad7 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/events/InMemoryEvents.java @@ -0,0 +1,79 @@ +/* + * 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.impl.events; + +import io.cloudevents.CloudEvent; +import io.serverlessworkflow.impl.DefaultExecutorServiceFactory; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +/* + * Straightforward implementation of in memory event broker. + * User might invoke publish to simulate event reception. + */ +public class InMemoryEvents extends AbstractTypeConsumer implements EventPublisher { + + private static InMemoryEvents instance = new InMemoryEvents(); + + private InMemoryEvents() {} + + public static InMemoryEvents get() { + return instance; + } + + private Map> topicMap = new ConcurrentHashMap<>(); + + private AtomicReference> allConsumerRef = new AtomicReference<>(); + + @Override + protected void register(String topicName, Consumer consumer) { + topicMap.put(topicName, consumer); + } + + @Override + protected void unregister(String topicName) { + topicMap.remove(topicName); + } + + @Override + public CompletableFuture publish(CloudEvent ce) { + return CompletableFuture.runAsync( + () -> { + Consumer allConsumer = allConsumerRef.get(); + if (allConsumer != null) { + allConsumer.accept(ce); + } + Consumer consumer = topicMap.get(ce.getType()); + if (consumer != null) { + consumer.accept(ce); + } + }, + DefaultExecutorServiceFactory.instance().get()); + } + + @Override + protected void registerToAll(Consumer consumer) { + allConsumerRef.set(consumer); + } + + @Override + protected void unregisterFromAll() { + allConsumerRef.set(null); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/events/TypeEventRegistration.java b/impl/core/src/main/java/io/serverlessworkflow/impl/events/TypeEventRegistration.java new file mode 100644 index 00000000..8fdf2388 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/events/TypeEventRegistration.java @@ -0,0 +1,24 @@ +/* + * 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.impl.events; + +import io.cloudevents.CloudEvent; +import java.util.function.Consumer; + +public record TypeEventRegistration( + String type, Consumer consumer, CloudEventPredicate predicate) + implements EventRegistration {} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/events/TypeEventRegistrationBuilder.java b/impl/core/src/main/java/io/serverlessworkflow/impl/events/TypeEventRegistrationBuilder.java new file mode 100644 index 00000000..bd504a76 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/events/TypeEventRegistrationBuilder.java @@ -0,0 +1,20 @@ +/* + * 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.impl.events; + +public record TypeEventRegistrationBuilder(String type, CloudEventPredicate cePredicate) + implements EventRegistrationBuilder {} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java new file mode 100644 index 00000000..a9681f39 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java @@ -0,0 +1,229 @@ +/* + * 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.impl.executors; + +import static io.serverlessworkflow.impl.WorkflowUtils.buildWorkflowFilter; +import static io.serverlessworkflow.impl.WorkflowUtils.getSchemaValidator; + +import io.serverlessworkflow.api.types.Export; +import io.serverlessworkflow.api.types.FlowDirective; +import io.serverlessworkflow.api.types.Input; +import io.serverlessworkflow.api.types.Output; +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.WorkflowStatus; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import io.serverlessworkflow.impl.schema.SchemaValidator; +import java.time.Instant; +import java.util.Iterator; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public abstract class AbstractTaskExecutor implements TaskExecutor { + + protected final T task; + protected final String taskName; + protected final WorkflowPosition position; + private final Optional inputProcessor; + private final Optional outputProcessor; + private final Optional contextProcessor; + private final Optional inputSchemaValidator; + private final Optional outputSchemaValidator; + private final Optional contextSchemaValidator; + private final Optional ifFilter; + + public abstract static class AbstractTaskExecutorBuilder + implements TaskExecutorBuilder { + private Optional inputProcessor = Optional.empty(); + private Optional outputProcessor = Optional.empty(); + private Optional contextProcessor = Optional.empty(); + private Optional ifFilter = Optional.empty(); + private Optional inputSchemaValidator = Optional.empty(); + private Optional outputSchemaValidator = Optional.empty(); + private Optional contextSchemaValidator = Optional.empty(); + protected final WorkflowPosition position; + protected final T task; + protected final String taskName; + protected final WorkflowApplication application; + protected final Workflow workflow; + protected final ResourceLoader resourceLoader; + + private TaskExecutor instance; + + protected AbstractTaskExecutorBuilder( + WorkflowPosition position, + T task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader resourceLoader) { + this.workflow = workflow; + this.taskName = position.last().toString(); + this.position = position; + this.task = task; + this.application = application; + this.resourceLoader = resourceLoader; + if (task.getInput() != null) { + Input input = task.getInput(); + this.inputProcessor = buildWorkflowFilter(application, input.getFrom()); + this.inputSchemaValidator = + getSchemaValidator(application.validatorFactory(), resourceLoader, input.getSchema()); + } + if (task.getOutput() != null) { + Output output = task.getOutput(); + this.outputProcessor = buildWorkflowFilter(application, output.getAs()); + this.outputSchemaValidator = + getSchemaValidator(application.validatorFactory(), resourceLoader, output.getSchema()); + } + if (task.getExport() != null) { + Export export = task.getExport(); + if (export.getAs() != null) { + this.contextProcessor = buildWorkflowFilter(application, export.getAs()); + } + this.contextSchemaValidator = + getSchemaValidator(application.validatorFactory(), resourceLoader, export.getSchema()); + } + this.ifFilter = application.expressionFactory().buildIfFilter(task); + } + + protected final TransitionInfoBuilder next( + FlowDirective flowDirective, Map> connections) { + if (flowDirective == null) { + return TransitionInfoBuilder.of(next(connections)); + } + if (flowDirective.getFlowDirectiveEnum() != null) { + switch (flowDirective.getFlowDirectiveEnum()) { + case CONTINUE: + return TransitionInfoBuilder.of(next(connections)); + case END: + return TransitionInfoBuilder.end(); + case EXIT: + return TransitionInfoBuilder.exit(); + } + } + return TransitionInfoBuilder.of(connections.get(flowDirective.getString())); + } + + private TaskExecutorBuilder next(Map> connections) { + Iterator> iter = connections.values().iterator(); + TaskExecutorBuilder next = null; + while (iter.hasNext()) { + TaskExecutorBuilder item = iter.next(); + if (item == this) { + next = iter.hasNext() ? iter.next() : null; + break; + } + } + return next; + } + + public TaskExecutor build() { + if (instance == null) { + instance = buildInstance(); + } + return instance; + } + + protected abstract TaskExecutor buildInstance(); + } + + protected AbstractTaskExecutor(AbstractTaskExecutorBuilder builder) { + this.task = builder.task; + this.taskName = builder.taskName; + this.position = builder.position; + this.inputProcessor = builder.inputProcessor; + this.outputProcessor = builder.outputProcessor; + this.contextProcessor = builder.contextProcessor; + this.inputSchemaValidator = builder.inputSchemaValidator; + this.outputSchemaValidator = builder.outputSchemaValidator; + this.contextSchemaValidator = builder.contextSchemaValidator; + this.ifFilter = builder.ifFilter; + } + + protected final CompletableFuture executeNext( + CompletableFuture future, WorkflowContext workflow) { + return future.thenCompose( + t -> { + TransitionInfo transition = t.transition(); + if (transition.isEndNode()) { + workflow.instance().status(WorkflowStatus.COMPLETED); + } else if (transition.next() != null) { + return transition.next().apply(workflow, t.parent(), t.output()); + } + return CompletableFuture.completedFuture(t); + }); + } + + @Override + public CompletableFuture apply( + WorkflowContext workflowContext, Optional parentContext, WorkflowModel input) { + TaskContext taskContext = new TaskContext(input, position, parentContext, taskName, task); + CompletableFuture completable = CompletableFuture.completedFuture(taskContext); + if (!TaskExecutorHelper.isActive(workflowContext)) { + return completable; + } + if (ifFilter + .flatMap(f -> f.apply(workflowContext, taskContext, input).asBoolean()) + .orElse(true)) { + return executeNext( + completable + .thenApply( + t -> { + workflowContext + .definition() + .listeners() + .forEach(l -> l.onTaskStarted(position, task)); + inputSchemaValidator.ifPresent(s -> s.validate(t.rawInput())); + inputProcessor.ifPresent( + p -> taskContext.input(p.apply(workflowContext, t, t.rawInput()))); + return t; + }) + .thenCompose(t -> execute(workflowContext, t)) + .thenApply( + t -> { + outputProcessor.ifPresent( + p -> t.output(p.apply(workflowContext, t, t.rawOutput()))); + outputSchemaValidator.ifPresent(s -> s.validate(t.output())); + contextProcessor.ifPresent( + p -> + workflowContext.context( + p.apply(workflowContext, t, workflowContext.context()))); + contextSchemaValidator.ifPresent(s -> s.validate(workflowContext.context())); + t.completedAt(Instant.now()); + workflowContext + .definition() + .listeners() + .forEach(l -> l.onTaskEnded(position, task)); + return t; + }), + workflowContext); + } else { + taskContext.transition(getSkipTransition()); + return executeNext(completable, workflowContext); + } + } + + protected abstract TransitionInfo getSkipTransition(); + + protected abstract CompletableFuture execute( + WorkflowContext workflow, TaskContext taskContext); +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/CallTaskExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/CallTaskExecutor.java new file mode 100644 index 00000000..1dc07645 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/CallTaskExecutor.java @@ -0,0 +1,64 @@ +/* + * 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.impl.executors; + +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.util.concurrent.CompletableFuture; + +public class CallTaskExecutor extends RegularTaskExecutor { + + private final CallableTask callable; + + public static class CallTaskExecutorBuilder + extends RegularTaskExecutorBuilder { + private CallableTask callable; + + protected CallTaskExecutorBuilder( + WorkflowPosition position, + T task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader resourceLoader, + CallableTask callable) { + super(position, task, workflow, application, resourceLoader); + this.callable = callable; + callable.init(task, workflow, application, resourceLoader); + } + + @Override + public TaskExecutor buildInstance() { + return new CallTaskExecutor<>(this); + } + } + + protected CallTaskExecutor(CallTaskExecutorBuilder builder) { + super(builder); + this.callable = builder.callable; + } + + @Override + protected CompletableFuture internalExecute( + WorkflowContext workflow, TaskContext taskContext) { + return callable.apply(workflow, taskContext, taskContext.input()); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/CallableTask.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/CallableTask.java new file mode 100644 index 00000000..6cc52922 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/CallableTask.java @@ -0,0 +1,35 @@ +/* + * 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.impl.executors; + +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.util.concurrent.CompletableFuture; + +public interface CallableTask { + default void init( + T task, Workflow workflow, WorkflowApplication application, ResourceLoader loader) {} + + CompletableFuture apply( + WorkflowContext workflowContext, TaskContext taskContext, WorkflowModel input); + + boolean accept(Class clazz); +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/DefaultTaskExecutorFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/DefaultTaskExecutorFactory.java new file mode 100644 index 00000000..bf63b5ed --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/DefaultTaskExecutorFactory.java @@ -0,0 +1,114 @@ +/* + * 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.impl.executors; + +import io.serverlessworkflow.api.types.CallTask; +import io.serverlessworkflow.api.types.Task; +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.executors.CallTaskExecutor.CallTaskExecutorBuilder; +import io.serverlessworkflow.impl.executors.DoExecutor.DoExecutorBuilder; +import io.serverlessworkflow.impl.executors.EmitExecutor.EmitExecutorBuilder; +import io.serverlessworkflow.impl.executors.ForExecutor.ForExecutorBuilder; +import io.serverlessworkflow.impl.executors.ForkExecutor.ForkExecutorBuilder; +import io.serverlessworkflow.impl.executors.ListenExecutor.ListenExecutorBuilder; +import io.serverlessworkflow.impl.executors.RaiseExecutor.RaiseExecutorBuilder; +import io.serverlessworkflow.impl.executors.SetExecutor.SetExecutorBuilder; +import io.serverlessworkflow.impl.executors.SwitchExecutor.SwitchExecutorBuilder; +import io.serverlessworkflow.impl.executors.TryExecutor.TryExecutorBuilder; +import io.serverlessworkflow.impl.executors.WaitExecutor.WaitExecutorBuilder; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.util.ServiceLoader; +import java.util.ServiceLoader.Provider; + +public class DefaultTaskExecutorFactory implements TaskExecutorFactory { + + private static TaskExecutorFactory instance = new DefaultTaskExecutorFactory(); + + public static TaskExecutorFactory get() { + return instance; + } + + protected DefaultTaskExecutorFactory() {} + + private ServiceLoader callTasks = ServiceLoader.load(CallableTask.class); + + @Override + public TaskExecutorBuilder getTaskExecutor( + WorkflowPosition position, + Task task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader resourceLoader) { + if (task.getCallTask() != null) { + CallTask callTask = task.getCallTask(); + TaskBase taskBase = (TaskBase) callTask.get(); + if (taskBase != null) { + return new CallTaskExecutorBuilder( + position, + taskBase, + workflow, + application, + resourceLoader, + findCallTask(taskBase.getClass())); + } + } else if (task.getSwitchTask() != null) { + return new SwitchExecutorBuilder( + position, task.getSwitchTask(), workflow, application, resourceLoader); + } else if (task.getDoTask() != null) { + return new DoExecutorBuilder( + position, task.getDoTask(), workflow, application, resourceLoader); + } else if (task.getSetTask() != null) { + return new SetExecutorBuilder( + position, task.getSetTask(), workflow, application, resourceLoader); + } else if (task.getForTask() != null) { + return new ForExecutorBuilder( + position, task.getForTask(), workflow, application, resourceLoader); + } else if (task.getRaiseTask() != null) { + return new RaiseExecutorBuilder( + position, task.getRaiseTask(), workflow, application, resourceLoader); + } else if (task.getTryTask() != null) { + return new TryExecutorBuilder( + position, task.getTryTask(), workflow, application, resourceLoader); + } else if (task.getForkTask() != null) { + return new ForkExecutorBuilder( + position, task.getForkTask(), workflow, application, resourceLoader); + } else if (task.getWaitTask() != null) { + return new WaitExecutorBuilder( + position, task.getWaitTask(), workflow, application, resourceLoader); + } else if (task.getListenTask() != null) { + return new ListenExecutorBuilder( + position, task.getListenTask(), workflow, application, resourceLoader); + } else if (task.getEmitTask() != null) { + return new EmitExecutorBuilder( + position, task.getEmitTask(), workflow, application, resourceLoader); + } + throw new UnsupportedOperationException(task.get().getClass().getName() + " not supported yet"); + } + + @SuppressWarnings("unchecked") + private CallableTask findCallTask(Class clazz) { + return (CallableTask) + callTasks.stream() + .map(Provider::get) + .filter(s -> s.accept(clazz)) + .findAny() + .orElseThrow( + () -> new UnsupportedOperationException(clazz.getName() + " not supported yet")); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/DoExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/DoExecutor.java new file mode 100644 index 00000000..65e3469b --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/DoExecutor.java @@ -0,0 +1,65 @@ +/* + * 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.impl.executors; + +import io.serverlessworkflow.api.types.DoTask; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public class DoExecutor extends RegularTaskExecutor { + + private final TaskExecutor taskExecutor; + + public static class DoExecutorBuilder extends RegularTaskExecutorBuilder { + private TaskExecutor taskExecutor; + + protected DoExecutorBuilder( + WorkflowPosition position, + DoTask task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader resourceLoader) { + super(position, task, workflow, application, resourceLoader); + taskExecutor = + TaskExecutorHelper.createExecutorList( + position, task.getDo(), workflow, application, resourceLoader); + } + + @Override + public TaskExecutor buildInstance() { + return new DoExecutor(this); + } + } + + private DoExecutor(DoExecutorBuilder builder) { + super(builder); + this.taskExecutor = builder.taskExecutor; + } + + @Override + protected CompletableFuture internalExecute( + WorkflowContext workflow, TaskContext taskContext) { + return TaskExecutorHelper.processTaskList( + taskExecutor, workflow, Optional.of(taskContext), taskContext.input()); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/EmitExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/EmitExecutor.java new file mode 100644 index 00000000..1c7f99df --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/EmitExecutor.java @@ -0,0 +1,239 @@ +/* + * 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.impl.executors; + +import io.cloudevents.CloudEvent; +import io.cloudevents.core.builder.CloudEventBuilder; +import io.serverlessworkflow.api.types.EmitTask; +import io.serverlessworkflow.api.types.EventData; +import io.serverlessworkflow.api.types.EventDataschema; +import io.serverlessworkflow.api.types.EventProperties; +import io.serverlessworkflow.api.types.EventSource; +import io.serverlessworkflow.api.types.EventTime; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.ExpressionHolder; +import io.serverlessworkflow.impl.StringFilter; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.WorkflowUtils; +import io.serverlessworkflow.impl.events.CloudEventUtils; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.net.URI; +import java.time.OffsetDateTime; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public class EmitExecutor extends RegularTaskExecutor { + + private final EventPropertiesBuilder props; + + public static class EmitExecutorBuilder extends RegularTaskExecutorBuilder { + + private EventPropertiesBuilder eventBuilder; + + protected EmitExecutorBuilder( + WorkflowPosition position, + EmitTask task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader resourceLoader) { + super(position, task, workflow, application, resourceLoader); + this.eventBuilder = + EventPropertiesBuilder.build(task.getEmit().getEvent().getWith(), application); + } + + @Override + public TaskExecutor buildInstance() { + return new EmitExecutor(this); + } + } + + private EmitExecutor(EmitExecutorBuilder builder) { + super(builder); + this.props = builder.eventBuilder; + } + + @Override + protected CompletableFuture internalExecute( + WorkflowContext workflow, TaskContext taskContext) { + return workflow + .definition() + .application() + .eventPublisher() + .publish(buildCloudEvent(workflow, taskContext)) + .thenApply(v -> taskContext.input()); + } + + private CloudEvent buildCloudEvent(WorkflowContext workflow, TaskContext taskContext) { + io.cloudevents.core.v1.CloudEventBuilder ceBuilder = CloudEventBuilder.v1(); + ceBuilder.withId( + props + .idFilter() + .map(filter -> filter.apply(workflow, taskContext)) + .orElse(UUID.randomUUID().toString())); + ceBuilder.withSource( + props + .sourceFilter() + .map(filter -> filter.apply(workflow, taskContext)) + .map(URI::create) + .orElse(URI.create("reference-impl"))); + ceBuilder.withType( + props + .typeFilter() + .map(filter -> filter.apply(workflow, taskContext)) + .orElseThrow( + () -> new IllegalArgumentException("Type is required for emitting events"))); + props + .timeFilter() + .map(filter -> filter.apply(workflow, taskContext)) + .ifPresent(value -> ceBuilder.withTime(value)); + props + .subjectFilter() + .map(filter -> filter.apply(workflow, taskContext)) + .ifPresent(value -> ceBuilder.withSubject(value)); + props + .dataSchemaFilter() + .map(filter -> filter.apply(workflow, taskContext)) + .ifPresent(value -> ceBuilder.withDataSchema(URI.create(value))); + props + .contentTypeFilter() + .map(filter -> filter.apply(workflow, taskContext)) + .ifPresent(value -> ceBuilder.withDataContentType(value)); + props + .dataFilter() + .map(filter -> filter.apply(workflow, taskContext, taskContext.input())) + .ifPresent( + value -> + ceBuilder.withData( + value + .asCloudEventData() + .orElseThrow( + () -> + new IllegalArgumentException( + "Workflow model " + + value + + " cannot be converted to CloudEvent")))); + // TODO JsonCloudEventData.wrap(value) + props + .additionalFilter() + .map(filter -> filter.apply(workflow, taskContext, taskContext.input())) + .ifPresent(value -> value.forEach((k, v) -> addExtension(ceBuilder, k, v))); + + return ceBuilder.build(); + } + + private static CloudEventBuilder addExtension( + CloudEventBuilder builder, String name, WorkflowModel value) { + value + .asText() + .ifPresentOrElse( + v -> builder.withExtension(name, v), + () -> + value + .asBoolean() + .ifPresentOrElse( + v -> builder.withExtension(name, v), + () -> value.asNumber().ifPresent(v -> builder.withExtension(name, v)))); + return builder; + } + + private static record EventPropertiesBuilder( + Optional idFilter, + Optional sourceFilter, + Optional subjectFilter, + Optional contentTypeFilter, + Optional typeFilter, + Optional dataSchemaFilter, + Optional> timeFilter, + Optional dataFilter, + Optional additionalFilter) { + + public static EventPropertiesBuilder build( + EventProperties properties, WorkflowApplication app) { + Optional idFilter = buildFilter(app, properties.getId()); + EventSource source = properties.getSource(); + Optional sourceFilter = + source == null + ? Optional.empty() + : Optional.of( + WorkflowUtils.buildStringFilter( + app, + source.getRuntimeExpression(), + WorkflowUtils.toString(source.getUriTemplate()))); + Optional subjectFilter = buildFilter(app, properties.getSubject()); + Optional contentTypeFilter = buildFilter(app, properties.getDatacontenttype()); + Optional typeFilter = buildFilter(app, properties.getType()); + EventDataschema dataSchema = properties.getDataschema(); + Optional dataSchemaFilter = + dataSchema == null + ? Optional.empty() + : Optional.of( + WorkflowUtils.buildStringFilter( + app, + dataSchema.getExpressionDataSchema(), + WorkflowUtils.toString(dataSchema.getLiteralDataSchema()))); + EventTime time = properties.getTime(); + Optional> timeFilter = + time == null + ? Optional.empty() + : Optional.of( + WorkflowUtils.buildExpressionHolder( + app, + time.getRuntimeExpression(), + CloudEventUtils.toOffset(time.getLiteralTime()), + v -> + v.asDate() + .orElseThrow( + () -> + new IllegalArgumentException( + "Expression does not generate a valid date")))); + EventData data = properties.getData(); + Optional dataFilter = + properties.getData() == null + ? Optional.empty() + : Optional.of( + WorkflowUtils.buildWorkflowFilter( + app, data.getRuntimeExpression(), data.getObject())); + Map ceAttrs = properties.getAdditionalProperties(); + Optional additionalFilter = + ceAttrs == null || ceAttrs.isEmpty() + ? Optional.empty() + : Optional.of(WorkflowUtils.buildWorkflowFilter(app, null, ceAttrs)); + return new EventPropertiesBuilder( + idFilter, + sourceFilter, + subjectFilter, + contentTypeFilter, + typeFilter, + dataSchemaFilter, + timeFilter, + dataFilter, + additionalFilter); + } + + private static Optional buildFilter(WorkflowApplication appl, String str) { + return str == null + ? Optional.empty() + : Optional.of(WorkflowUtils.buildStringFilter(appl, str)); + } + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForExecutor.java new file mode 100644 index 00000000..977152ec --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForExecutor.java @@ -0,0 +1,105 @@ +/* + * 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.impl.executors; + +import io.serverlessworkflow.api.types.ForTask; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.WorkflowUtils; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.util.Iterator; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public class ForExecutor extends RegularTaskExecutor { + + private final WorkflowFilter collectionExpr; + private final Optional whileExpr; + private final TaskExecutor taskExecutor; + + public static class ForExecutorBuilder extends RegularTaskExecutorBuilder { + private WorkflowFilter collectionExpr; + private Optional whileExpr; + private TaskExecutor taskExecutor; + + protected ForExecutorBuilder( + WorkflowPosition position, + ForTask task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader resourceLoader) { + super(position, task, workflow, application, resourceLoader); + this.collectionExpr = buildCollectionFilter(); + this.whileExpr = buildWhileFilter(); + this.taskExecutor = + TaskExecutorHelper.createExecutorList( + position, task.getDo(), workflow, application, resourceLoader); + } + + protected Optional buildWhileFilter() { + return WorkflowUtils.optionalFilter(application, task.getWhile()); + } + + protected WorkflowFilter buildCollectionFilter() { + return WorkflowUtils.buildWorkflowFilter(application, task.getFor().getIn()); + } + + @Override + public TaskExecutor buildInstance() { + return new ForExecutor(this); + } + } + + protected ForExecutor(ForExecutorBuilder builder) { + super(builder); + this.collectionExpr = builder.collectionExpr; + this.whileExpr = builder.whileExpr; + this.taskExecutor = builder.taskExecutor; + } + + @Override + protected CompletableFuture internalExecute( + WorkflowContext workflow, TaskContext taskContext) { + Iterator iter = + collectionExpr.apply(workflow, taskContext, taskContext.input()).asCollection().iterator(); + int i = 0; + CompletableFuture future = + CompletableFuture.completedFuture(taskContext.input()); + while (iter.hasNext()) { + WorkflowModel item = iter.next(); + taskContext.variables().put(task.getFor().getEach(), item); + taskContext.variables().put(task.getFor().getAt(), i++); + if (whileExpr + .map(w -> w.apply(workflow, taskContext, taskContext.input())) + .map(n -> n.asBoolean().orElse(true)) + .orElse(true)) { + future = + future.thenCompose( + input -> + TaskExecutorHelper.processTaskList( + taskExecutor, workflow, Optional.of(taskContext), input)); + } else { + break; + } + } + return future; + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForkExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForkExecutor.java new file mode 100644 index 00000000..1353db89 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForkExecutor.java @@ -0,0 +1,119 @@ +/* + * 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.impl.executors; + +import io.serverlessworkflow.api.types.ForkTask; +import io.serverlessworkflow.api.types.ForkTaskConfiguration; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.executors.RegularTaskExecutor.RegularTaskExecutorBuilder; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class ForkExecutor extends RegularTaskExecutor { + + private final ExecutorService service; + private final Map> taskExecutors; + + private final boolean compete; + + public static class ForkExecutorBuilder extends RegularTaskExecutorBuilder { + + private final Map> taskExecutors; + private final boolean compete; + + protected ForkExecutorBuilder( + WorkflowPosition position, + ForkTask task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader resourceLoader) { + super(position, task, workflow, application, resourceLoader); + ForkTaskConfiguration forkConfig = task.getFork(); + this.taskExecutors = + TaskExecutorHelper.createBranchList( + position, forkConfig.getBranches(), workflow, application, resourceLoader); + this.compete = forkConfig.isCompete(); + } + + @Override + public TaskExecutor buildInstance() { + return new ForkExecutor(this); + } + } + + protected ForkExecutor(ForkExecutorBuilder builder) { + super(builder); + service = builder.application.executorService(); + this.taskExecutors = builder.taskExecutors; + this.compete = builder.compete; + } + + @Override + protected CompletableFuture internalExecute( + WorkflowContext workflow, TaskContext taskContext) { + Map> futures = new HashMap<>(); + CompletableFuture initial = CompletableFuture.completedFuture(taskContext); + for (Map.Entry> entry : taskExecutors.entrySet()) { + futures.put( + entry.getKey(), + initial.thenComposeAsync( + t -> entry.getValue().apply(workflow, Optional.of(t), t.input()), service)); + } + return CompletableFuture.allOf( + futures.values().toArray(new CompletableFuture[futures.size()])) + .thenApply( + i -> + combine( + workflow, + futures.entrySet().stream() + .collect(Collectors.toMap(Entry::getKey, e -> e.getValue().join())))); + } + + private WorkflowModel combine(WorkflowContext context, Map futures) { + + Stream> sortedStream = + futures.entrySet().stream() + .sorted( + (arg1, arg2) -> + arg1.getValue().completedAt().compareTo(arg2.getValue().completedAt())); + return compete + ? sortedStream.map(e -> e.getValue().output()).findFirst().orElseThrow() + : context + .definition() + .application() + .modelFactory() + .combine( + sortedStream.collect( + Collectors.toMap( + Entry::getKey, + e -> e.getValue().output(), + (x, y) -> y, + LinkedHashMap::new))); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ListenExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ListenExecutor.java new file mode 100644 index 00000000..ebede8c1 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ListenExecutor.java @@ -0,0 +1,308 @@ +/* + * 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.impl.executors; + +import io.cloudevents.CloudEvent; +import io.serverlessworkflow.api.types.AllEventConsumptionStrategy; +import io.serverlessworkflow.api.types.AnyEventConsumptionStrategy; +import io.serverlessworkflow.api.types.EventConsumptionStrategy; +import io.serverlessworkflow.api.types.EventFilter; +import io.serverlessworkflow.api.types.ListenTask; +import io.serverlessworkflow.api.types.ListenTaskConfiguration; +import io.serverlessworkflow.api.types.ListenTaskConfiguration.ListenAndReadAs; +import io.serverlessworkflow.api.types.ListenTo; +import io.serverlessworkflow.api.types.OneEventConsumptionStrategy; +import io.serverlessworkflow.api.types.SubscriptionIterator; +import io.serverlessworkflow.api.types.Until; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowModelCollection; +import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.WorkflowStatus; +import io.serverlessworkflow.impl.WorkflowUtils; +import io.serverlessworkflow.impl.events.EventConsumer; +import io.serverlessworkflow.impl.events.EventRegistration; +import io.serverlessworkflow.impl.events.EventRegistrationBuilder; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.stream.Collectors; + +public abstract class ListenExecutor extends RegularTaskExecutor { + + protected final EventRegistrationBuilderCollection regBuilders; + protected final Optional> loop; + protected final Function converter; + protected final EventConsumer eventConsumer; + + private static record EventRegistrationBuilderCollection( + Collection registrations, boolean isAnd) {} + + public static class ListenExecutorBuilder extends RegularTaskExecutorBuilder { + + private EventRegistrationBuilderCollection registrations; + private WorkflowFilter until; + private EventRegistrationBuilderCollection untilRegistrations; + private TaskExecutor loop; + private Function converter = + ce -> application.modelFactory().from(ce.getData()); + + private EventRegistrationBuilderCollection allEvents(AllEventConsumptionStrategy allStrategy) { + return new EventRegistrationBuilderCollection(from(allStrategy.getAll()), true); + } + + private EventRegistrationBuilderCollection anyEvents(AnyEventConsumptionStrategy anyStrategy) { + List eventFilters = anyStrategy.getAny(); + return new EventRegistrationBuilderCollection( + eventFilters.isEmpty() ? registerToAll() : from(eventFilters), false); + } + + private EventRegistrationBuilderCollection oneEvent(OneEventConsumptionStrategy oneStrategy) { + return new EventRegistrationBuilderCollection(List.of(from(oneStrategy.getOne())), true); + } + + protected ListenExecutorBuilder( + WorkflowPosition position, + ListenTask task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader resourceLoader) { + super(position, task, workflow, application, resourceLoader); + ListenTaskConfiguration listen = task.getListen(); + ListenTo to = listen.getTo(); + if (to.getAllEventConsumptionStrategy() != null) { + registrations = allEvents(to.getAllEventConsumptionStrategy()); + } else if (to.getAnyEventConsumptionStrategy() != null) { + AnyEventConsumptionStrategy any = to.getAnyEventConsumptionStrategy(); + registrations = anyEvents(any); + Until untilDesc = any.getUntil(); + if (untilDesc != null) { + if (untilDesc.getAnyEventUntilCondition() != null) { + until = + WorkflowUtils.buildWorkflowFilter( + application, untilDesc.getAnyEventUntilCondition()); + } else if (untilDesc.getAnyEventUntilConsumed() != null) { + EventConsumptionStrategy strategy = untilDesc.getAnyEventUntilConsumed(); + if (strategy.getAllEventConsumptionStrategy() != null) { + untilRegistrations = allEvents(strategy.getAllEventConsumptionStrategy()); + } else if (strategy.getAnyEventConsumptionStrategy() != null) { + untilRegistrations = anyEvents(strategy.getAnyEventConsumptionStrategy()); + } else if (strategy.getOneEventConsumptionStrategy() != null) { + untilRegistrations = oneEvent(strategy.getOneEventConsumptionStrategy()); + } + } + } + } else if (to.getOneEventConsumptionStrategy() != null) { + registrations = oneEvent(to.getOneEventConsumptionStrategy()); + } + SubscriptionIterator forEach = task.getForeach(); + if (forEach != null) { + loop = + TaskExecutorHelper.createExecutorList( + position, forEach.getDo(), workflow, application, resourceLoader); + } + ListenAndReadAs readAs = listen.getRead(); + if (readAs != null) { + switch (readAs) { + case ENVELOPE: + converter = ce -> application.modelFactory().from(ce); + default: + case DATA: + converter = ce -> application.modelFactory().from(ce.getData()); + break; + } + } + } + + private Collection registerToAll() { + return application.eventConsumer().listenToAll(application); + } + + private Collection from(List filters) { + return filters.stream().map(this::from).collect(Collectors.toList()); + } + + private EventRegistrationBuilder from(EventFilter filter) { + return application.eventConsumer().listen(filter, application); + } + + @Override + public TaskExecutor buildInstance() { + return registrations.isAnd() ? new AndListenExecutor(this) : new OrListenExecutor(this); + } + } + + public static class AndListenExecutor extends ListenExecutor { + + public AndListenExecutor(ListenExecutorBuilder builder) { + super(builder); + } + + protected void internalProcessCe( + WorkflowModel node, + WorkflowModelCollection arrayNode, + WorkflowContext workflow, + TaskContext taskContext, + CompletableFuture future) { + arrayNode.add(node); + future.complete(node); + } + } + + public static class OrListenExecutor extends ListenExecutor { + + private final Optional until; + private final EventRegistrationBuilderCollection untilRegBuilders; + + public OrListenExecutor(ListenExecutorBuilder builder) { + super(builder); + this.until = Optional.ofNullable(builder.until); + this.untilRegBuilders = builder.untilRegistrations; + } + + @Override + protected CompletableFuture buildFuture( + EventRegistrationBuilderCollection regCollection, + Collection registrations, + BiConsumer> consumer) { + CompletableFuture combinedFuture = + super.buildFuture(regCollection, registrations, consumer); + if (untilRegBuilders != null) { + Collection untilRegistrations = new ArrayList<>(); + CompletableFuture untilFuture = + combine(untilRegBuilders, untilRegistrations, (ce, f) -> f.complete(null)); + untilFuture.thenAccept( + v -> { + combinedFuture.complete(null); + untilRegistrations.forEach(reg -> eventConsumer.unregister(reg)); + }); + } + return combinedFuture; + } + + protected void internalProcessCe( + WorkflowModel node, + WorkflowModelCollection arrayNode, + WorkflowContext workflow, + TaskContext taskContext, + CompletableFuture future) { + arrayNode.add(node); + if ((until.isEmpty() + || until.map(u -> u.apply(workflow, taskContext, arrayNode).asBoolean()).isPresent()) + && untilRegBuilders == null) { + future.complete(node); + } + } + } + + protected abstract void internalProcessCe( + WorkflowModel node, + WorkflowModelCollection arrayNode, + WorkflowContext workflow, + TaskContext taskContext, + CompletableFuture future); + + @Override + protected CompletableFuture internalExecute( + WorkflowContext workflow, TaskContext taskContext) { + WorkflowModelCollection output = + workflow.definition().application().modelFactory().createCollection(); + Collection registrations = new ArrayList<>(); + workflow.instance().status(WorkflowStatus.WAITING); + return buildFuture( + regBuilders, + registrations, + (BiConsumer>) + ((ce, future) -> + processCe(converter.apply(ce), output, workflow, taskContext, future))) + .thenApply( + v -> { + workflow.instance().status(WorkflowStatus.RUNNING); + registrations.forEach(reg -> eventConsumer.unregister(reg)); + return output; + }); + } + + protected CompletableFuture buildFuture( + EventRegistrationBuilderCollection regCollection, + Collection registrations, + BiConsumer> consumer) { + return combine(regCollection, registrations, consumer); + } + + protected final CompletableFuture combine( + EventRegistrationBuilderCollection regCollection, + Collection registrations, + BiConsumer> consumer) { + CompletableFuture[] futures = + regCollection.registrations().stream() + .map(reg -> toCompletable(reg, registrations, consumer)) + .toArray(size -> new CompletableFuture[size]); + return regCollection.isAnd() + ? CompletableFuture.allOf(futures) + : CompletableFuture.anyOf(futures); + } + + private CompletableFuture toCompletable( + EventRegistrationBuilder regBuilder, + Collection registrations, + BiConsumer> ceConsumer) { + final CompletableFuture future = new CompletableFuture<>(); + registrations.add( + eventConsumer.register(regBuilder, ce -> ceConsumer.accept((CloudEvent) ce, future))); + return future; + } + + private void processCe( + WorkflowModel node, + WorkflowModelCollection arrayNode, + WorkflowContext workflow, + TaskContext taskContext, + CompletableFuture future) { + loop.ifPresentOrElse( + t -> { + SubscriptionIterator forEach = task.getForeach(); + String item = forEach.getItem(); + if (item != null) { + taskContext.variables().put(item, node); + } + String at = forEach.getAt(); + if (at != null) { + taskContext.variables().put(at, arrayNode.size()); + } + TaskExecutorHelper.processTaskList(t, workflow, Optional.of(taskContext), node) + .thenAccept(n -> internalProcessCe(n, arrayNode, workflow, taskContext, future)); + }, + () -> internalProcessCe(node, arrayNode, workflow, taskContext, future)); + } + + protected ListenExecutor(ListenExecutorBuilder builder) { + super(builder); + this.eventConsumer = builder.application.eventConsumer(); + this.regBuilders = builder.registrations; + this.loop = Optional.ofNullable(builder.loop); + this.converter = builder.converter; + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RaiseExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RaiseExecutor.java new file mode 100644 index 00000000..27c9018f --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RaiseExecutor.java @@ -0,0 +1,131 @@ +/* + * 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.impl.executors; + +import io.serverlessworkflow.api.types.Error; +import io.serverlessworkflow.api.types.ErrorInstance; +import io.serverlessworkflow.api.types.ErrorType; +import io.serverlessworkflow.api.types.RaiseTask; +import io.serverlessworkflow.api.types.RaiseTaskError; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.StringFilter; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowError; +import io.serverlessworkflow.impl.WorkflowException; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.WorkflowUtils; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiFunction; + +public class RaiseExecutor extends RegularTaskExecutor { + + private final BiFunction errorBuilder; + + public static class RaiseExecutorBuilder extends RegularTaskExecutorBuilder { + + private final BiFunction errorBuilder; + private final StringFilter typeFilter; + private final Optional instanceFilter; + private final StringFilter titleFilter; + private final StringFilter detailFilter; + + protected RaiseExecutorBuilder( + WorkflowPosition position, + RaiseTask task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader resourceLoader) { + super(position, task, workflow, application, resourceLoader); + RaiseTaskError raiseError = task.getRaise().getError(); + Error error = + raiseError.getRaiseErrorDefinition() != null + ? raiseError.getRaiseErrorDefinition() + : findError(raiseError.getRaiseErrorReference()); + this.typeFilter = getTypeFunction(application, error.getType()); + this.instanceFilter = getInstanceFunction(application, error.getInstance()); + this.titleFilter = + WorkflowUtils.buildStringFilter( + application, + error.getTitle().getExpressionErrorTitle(), + error.getTitle().getLiteralErrorTitle()); + this.detailFilter = + WorkflowUtils.buildStringFilter( + application, + error.getDetail().getExpressionErrorDetails(), + error.getTitle().getExpressionErrorTitle()); + this.errorBuilder = (w, t) -> buildError(error, w, t); + } + + private WorkflowError buildError( + Error error, WorkflowContext context, TaskContext taskContext) { + return WorkflowError.error(typeFilter.apply(context, taskContext), error.getStatus()) + .instance( + instanceFilter + .map(f -> f.apply(context, taskContext)) + .orElseGet(() -> taskContext.position().jsonPointer())) + .title(titleFilter.apply(context, taskContext)) + .details(detailFilter.apply(context, taskContext)) + .build(); + } + + private Optional getInstanceFunction( + WorkflowApplication app, ErrorInstance errorInstance) { + return errorInstance != null + ? Optional.of( + WorkflowUtils.buildStringFilter( + app, + errorInstance.getExpressionErrorInstance(), + errorInstance.getLiteralErrorInstance())) + : Optional.empty(); + } + + private StringFilter getTypeFunction(WorkflowApplication app, ErrorType type) { + return WorkflowUtils.buildStringFilter( + app, type.getExpressionErrorType(), type.getLiteralErrorType().get().toString()); + } + + private Error findError(String raiseErrorReference) { + Map errorsMap = workflow.getUse().getErrors().getAdditionalProperties(); + Error error = errorsMap.get(raiseErrorReference); + if (error == null) { + throw new IllegalArgumentException("Error " + error + "is not defined in " + errorsMap); + } + return error; + } + + @Override + public TaskExecutor buildInstance() { + return new RaiseExecutor(this); + } + } + + protected RaiseExecutor(RaiseExecutorBuilder builder) { + super(builder); + this.errorBuilder = builder.errorBuilder; + } + + @Override + protected CompletableFuture internalExecute( + WorkflowContext workflow, TaskContext taskContext) { + throw new WorkflowException(errorBuilder.apply(workflow, taskContext)); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RegularTaskExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RegularTaskExecutor.java new file mode 100644 index 00000000..c4a716c9 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RegularTaskExecutor.java @@ -0,0 +1,72 @@ +/* + * 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.impl.executors; + +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public abstract class RegularTaskExecutor extends AbstractTaskExecutor { + + protected final TransitionInfo transition; + + protected RegularTaskExecutor(RegularTaskExecutorBuilder builder) { + super(builder); + this.transition = TransitionInfo.build(builder.transition); + } + + public abstract static class RegularTaskExecutorBuilder + extends AbstractTaskExecutorBuilder { + + private TransitionInfoBuilder transition; + + protected RegularTaskExecutorBuilder( + WorkflowPosition position, + T task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader resourceLoader) { + super(position, task, workflow, application, resourceLoader); + } + + public void connect(Map> connections) { + this.transition = next(task.getThen(), connections); + } + } + + @Override + protected TransitionInfo getSkipTransition() { + return transition; + } + + protected CompletableFuture execute( + WorkflowContext workflow, TaskContext taskContext) { + CompletableFuture future = + internalExecute(workflow, taskContext) + .thenApply(node -> taskContext.rawOutput(node).transition(transition)); + return future; + } + + protected abstract CompletableFuture internalExecute( + WorkflowContext workflow, TaskContext task); +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/SetExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/SetExecutor.java new file mode 100644 index 00000000..9dc8c0a5 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/SetExecutor.java @@ -0,0 +1,73 @@ +/* + * 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.impl.executors; + +import io.serverlessworkflow.api.types.Set; +import io.serverlessworkflow.api.types.SetTask; +import io.serverlessworkflow.api.types.SetTaskConfiguration; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.WorkflowUtils; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.util.concurrent.CompletableFuture; + +public class SetExecutor extends RegularTaskExecutor { + + private final WorkflowFilter setFilter; + + public static class SetExecutorBuilder extends RegularTaskExecutorBuilder { + + private final WorkflowFilter setFilter; + + protected SetExecutorBuilder( + WorkflowPosition position, + SetTask task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader resourceLoader) { + super(position, task, workflow, application, resourceLoader); + Set setInfo = task.getSet(); + SetTaskConfiguration setConfig = setInfo.getSetTaskConfiguration(); + this.setFilter = + WorkflowUtils.buildWorkflowFilter( + application, + setInfo.getString(), + setConfig != null ? setConfig.getAdditionalProperties() : null); + } + + @Override + public TaskExecutor buildInstance() { + return new SetExecutor(this); + } + } + + private SetExecutor(SetExecutorBuilder builder) { + super(builder); + this.setFilter = builder.setFilter; + } + + @Override + protected CompletableFuture internalExecute( + WorkflowContext workflow, TaskContext taskContext) { + return CompletableFuture.completedFuture( + setFilter.apply(workflow, taskContext, taskContext.input())); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/SwitchExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/SwitchExecutor.java new file mode 100644 index 00000000..19a69568 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/SwitchExecutor.java @@ -0,0 +1,113 @@ +/* + * 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.impl.executors; + +import io.serverlessworkflow.api.types.FlowDirective; +import io.serverlessworkflow.api.types.SwitchCase; +import io.serverlessworkflow.api.types.SwitchItem; +import io.serverlessworkflow.api.types.SwitchTask; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.WorkflowUtils; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +public class SwitchExecutor extends AbstractTaskExecutor { + + private final Map workflowFilters; + private final TransitionInfo defaultTask; + + public static class SwitchExecutorBuilder extends AbstractTaskExecutorBuilder { + private final Map workflowFilters = new HashMap<>(); + private Map switchFilters = new HashMap<>(); + private FlowDirective defaultDirective; + private TransitionInfoBuilder defaultTask; + + public SwitchExecutorBuilder( + WorkflowPosition position, + SwitchTask task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader resourceLoader) { + super(position, task, workflow, application, resourceLoader); + for (SwitchItem item : task.getSwitch()) { + SwitchCase switchCase = item.getSwitchCase(); + buildFilter(switchCase) + .ifPresentOrElse( + f -> workflowFilters.put(switchCase, f), + () -> defaultDirective = switchCase.getThen()); + } + } + + protected Optional buildFilter(SwitchCase switchCase) { + return switchCase.getWhen() != null + ? Optional.of(WorkflowUtils.buildWorkflowFilter(application, switchCase.getWhen())) + : Optional.empty(); + } + + @Override + public void connect(Map> connections) { + this.switchFilters = + this.workflowFilters.entrySet().stream() + .collect( + Collectors.toMap(Entry::getValue, e -> next(e.getKey().getThen(), connections))); + this.defaultTask = next(defaultDirective, connections); + } + + @Override + protected TaskExecutor buildInstance() { + return new SwitchExecutor(this); + } + } + + @Override + protected TransitionInfo getSkipTransition() { + return defaultTask; + } + + private SwitchExecutor(SwitchExecutorBuilder builder) { + super(builder); + this.defaultTask = TransitionInfo.build(builder.defaultTask); + this.workflowFilters = + builder.switchFilters.entrySet().stream() + .collect(Collectors.toMap(Entry::getKey, e -> TransitionInfo.build(e.getValue()))); + } + + @Override + protected CompletableFuture execute( + WorkflowContext workflow, TaskContext taskContext) { + CompletableFuture future = CompletableFuture.completedFuture(taskContext); + for (Entry entry : workflowFilters.entrySet()) { + if (entry + .getKey() + .apply(workflow, taskContext, taskContext.input()) + .asBoolean() + .orElse(false)) { + return future.thenApply(t -> t.transition(entry.getValue())); + } + } + return future.thenApply(t -> t.transition(defaultTask)); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutor.java new file mode 100644 index 00000000..ebf492f3 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutor.java @@ -0,0 +1,29 @@ +/* + * 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.impl.executors; + +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +@FunctionalInterface +public interface TaskExecutor { + CompletableFuture apply( + WorkflowContext workflowContext, Optional parentContext, WorkflowModel input); +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutorBuilder.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutorBuilder.java new file mode 100644 index 00000000..2cbb8c16 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutorBuilder.java @@ -0,0 +1,26 @@ +/* + * 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.impl.executors; + +import io.serverlessworkflow.api.types.TaskBase; +import java.util.Map; + +public interface TaskExecutorBuilder { + + void connect(Map> connections); + + TaskExecutor build(); +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutorFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutorFactory.java new file mode 100644 index 00000000..be1f1bee --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutorFactory.java @@ -0,0 +1,33 @@ +/* + * 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.impl.executors; + +import io.serverlessworkflow.api.types.Task; +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.ServicePriority; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.resources.ResourceLoader; + +public interface TaskExecutorFactory extends ServicePriority { + TaskExecutorBuilder getTaskExecutor( + WorkflowPosition position, + Task task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader resourceLoader); +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutorHelper.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutorHelper.java new file mode 100644 index 00000000..646a16f4 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TaskExecutorHelper.java @@ -0,0 +1,111 @@ +/* + * 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.impl.executors; + +import io.serverlessworkflow.api.types.TaskItem; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.WorkflowStatus; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +public class TaskExecutorHelper { + private TaskExecutorHelper() {} + + public static CompletableFuture processTaskList( + TaskExecutor taskExecutor, + WorkflowContext context, + Optional parentTask, + WorkflowModel input) { + return taskExecutor + .apply(context, parentTask, input) + .thenApply( + t -> { + parentTask.ifPresent(p -> p.rawOutput(t.output())); + return t.output(); + }); + } + + public static boolean isActive(WorkflowContext context) { + return isActive(context.instance().status()); + } + + public static boolean isActive(WorkflowStatus status) { + return status == WorkflowStatus.RUNNING || status == WorkflowStatus.WAITING; + } + + public static TaskExecutor createExecutorList( + WorkflowPosition position, + List taskItems, + Workflow workflow, + WorkflowApplication application, + ResourceLoader resourceLoader) { + Map> executors = + createExecutorBuilderList(position, taskItems, workflow, application, resourceLoader, "do"); + executors.values().forEach(t -> t.connect(executors)); + Iterator> iter = executors.values().iterator(); + TaskExecutor first = iter.next().build(); + while (iter.hasNext()) { + iter.next().build(); + } + return first; + } + + public static Map> createBranchList( + WorkflowPosition position, + List taskItems, + Workflow workflow, + WorkflowApplication application, + ResourceLoader resourceLoader) { + return createExecutorBuilderList( + position, taskItems, workflow, application, resourceLoader, "branch") + .entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().build())); + } + + private static Map> createExecutorBuilderList( + WorkflowPosition position, + List taskItems, + Workflow workflow, + WorkflowApplication application, + ResourceLoader resourceLoader, + String containerName) { + TaskExecutorFactory taskFactory = application.taskFactory(); + Map> executors = new LinkedHashMap<>(); + position.addProperty(containerName); + int index = 0; + for (TaskItem item : taskItems) { + position.addIndex(index++).addProperty(item.getName()); + TaskExecutorBuilder taskExecutorBuilder = + taskFactory.getTaskExecutor( + position.copy(), item.getTask(), workflow, application, resourceLoader); + executors.put(item.getName(), taskExecutorBuilder); + position.back().back(); + } + return executors; + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TransitionInfo.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TransitionInfo.java new file mode 100644 index 00000000..f330ac74 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TransitionInfo.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.impl.executors; + +public record TransitionInfo(TaskExecutor next, boolean isEndNode) { + private static final TransitionInfo END = new TransitionInfo(null, true); + private static final TransitionInfo EXIT = new TransitionInfo(null, false); + + static TransitionInfo build(TransitionInfoBuilder builder) { + if (builder == null || builder == TransitionInfoBuilder.exit()) return EXIT; + if (builder == TransitionInfoBuilder.end()) return END; + return new TransitionInfo(builder.next().build(), false); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TransitionInfoBuilder.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TransitionInfoBuilder.java new file mode 100644 index 00000000..7f62eaf7 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TransitionInfoBuilder.java @@ -0,0 +1,34 @@ +/* + * 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.impl.executors; + +public record TransitionInfoBuilder(TaskExecutorBuilder next, boolean isEndNode) { + + private static final TransitionInfoBuilder END = new TransitionInfoBuilder(null, true); + private static final TransitionInfoBuilder EXIT = new TransitionInfoBuilder(null, false); + + static TransitionInfoBuilder of(TaskExecutorBuilder next) { + return next == null ? EXIT : new TransitionInfoBuilder(next, false); + } + + static TransitionInfoBuilder end() { + return END; + } + + static TransitionInfoBuilder exit() { + return EXIT; + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TryExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TryExecutor.java new file mode 100644 index 00000000..b3efca9e --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/TryExecutor.java @@ -0,0 +1,152 @@ +/* + * 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.impl.executors; + +import io.serverlessworkflow.api.types.CatchErrors; +import io.serverlessworkflow.api.types.ErrorFilter; +import io.serverlessworkflow.api.types.TaskItem; +import io.serverlessworkflow.api.types.TryTask; +import io.serverlessworkflow.api.types.TryTaskCatch; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowError; +import io.serverlessworkflow.impl.WorkflowException; +import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.WorkflowUtils; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.function.Predicate; + +public class TryExecutor extends RegularTaskExecutor { + + private final Optional whenFilter; + private final Optional exceptFilter; + private final Optional> errorFilter; + private final TaskExecutor taskExecutor; + private final Optional> catchTaskExecutor; + + public static class TryExecutorBuilder extends RegularTaskExecutorBuilder { + + private final Optional whenFilter; + private final Optional exceptFilter; + private final Optional> errorFilter; + private final TaskExecutor taskExecutor; + private final Optional> catchTaskExecutor; + + protected TryExecutorBuilder( + WorkflowPosition position, + TryTask task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader resourceLoader) { + super(position, task, workflow, application, resourceLoader); + TryTaskCatch catchInfo = task.getCatch(); + this.errorFilter = buildErrorFilter(catchInfo.getErrors()); + this.whenFilter = WorkflowUtils.optionalFilter(application, catchInfo.getWhen()); + this.exceptFilter = WorkflowUtils.optionalFilter(application, catchInfo.getExceptWhen()); + this.taskExecutor = + TaskExecutorHelper.createExecutorList( + position, task.getTry(), workflow, application, resourceLoader); + List catchTask = task.getCatch().getDo(); + this.catchTaskExecutor = + catchTask != null && !catchTask.isEmpty() + ? Optional.of( + TaskExecutorHelper.createExecutorList( + position, task.getCatch().getDo(), workflow, application, resourceLoader)) + : Optional.empty(); + } + + @Override + public TaskExecutor buildInstance() { + return new TryExecutor(this); + } + } + + protected TryExecutor(TryExecutorBuilder builder) { + super(builder); + this.errorFilter = builder.errorFilter; + this.whenFilter = builder.whenFilter; + this.exceptFilter = builder.exceptFilter; + this.taskExecutor = builder.taskExecutor; + this.catchTaskExecutor = builder.catchTaskExecutor; + } + + @Override + protected CompletableFuture internalExecute( + WorkflowContext workflow, TaskContext taskContext) { + return TaskExecutorHelper.processTaskList( + taskExecutor, workflow, Optional.of(taskContext), taskContext.input()) + .exceptionallyCompose(e -> handleException(e, workflow, taskContext)); + } + + private CompletableFuture handleException( + Throwable e, WorkflowContext workflow, TaskContext taskContext) { + if (e instanceof CompletionException) { + return handleException(e.getCause(), workflow, taskContext); + } + if (e instanceof WorkflowException) { + WorkflowException exception = (WorkflowException) e; + if (errorFilter.map(f -> f.test(exception.getWorflowError())).orElse(true) + && whenFilter + .flatMap(w -> w.apply(workflow, taskContext, taskContext.input()).asBoolean()) + .orElse(true) + && exceptFilter + .map( + w -> + !w.apply(workflow, taskContext, taskContext.input()) + .asBoolean() + .orElse(false)) + .orElse(true)) { + if (catchTaskExecutor.isPresent()) { + return TaskExecutorHelper.processTaskList( + catchTaskExecutor.get(), workflow, Optional.of(taskContext), taskContext.input()); + } + } + return CompletableFuture.completedFuture(taskContext.rawOutput()); + } else { + if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } else { + throw new RuntimeException(e); + } + } + } + + private static Optional> buildErrorFilter(CatchErrors errors) { + return errors != null + ? Optional.of(error -> filterError(error, errors.getWith())) + : Optional.empty(); + } + + private static boolean filterError(WorkflowError error, ErrorFilter errorFilter) { + return compareString(errorFilter.getType(), error.type()) + && (errorFilter.getStatus() <= 0 || error.status() == errorFilter.getStatus()) + && compareString(errorFilter.getInstance(), error.instance()) + && compareString(errorFilter.getTitle(), error.title()) + && compareString(errorFilter.getDetails(), errorFilter.getDetails()); + } + + private static boolean compareString(String one, String other) { + return one == null || one.equals(other); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/WaitExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/WaitExecutor.java new file mode 100644 index 00000000..64ecde23 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/WaitExecutor.java @@ -0,0 +1,84 @@ +/* + * 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.impl.executors; + +import io.serverlessworkflow.api.types.DurationInline; +import io.serverlessworkflow.api.types.WaitTask; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.WorkflowStatus; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +public class WaitExecutor extends RegularTaskExecutor { + + private final Duration millisToWait; + + public static class WaitExecutorBuilder extends RegularTaskExecutorBuilder { + private final Duration millisToWait; + + protected WaitExecutorBuilder( + WorkflowPosition position, + WaitTask task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader resourceLoader) { + super(position, task, workflow, application, resourceLoader); + this.millisToWait = + task.getWait().getDurationInline() != null + ? toLong(task.getWait().getDurationInline()) + : Duration.parse(task.getWait().getDurationExpression()); + } + + private Duration toLong(DurationInline durationInline) { + Duration duration = Duration.ofMillis(durationInline.getMilliseconds()); + duration.plus(Duration.ofSeconds(durationInline.getSeconds())); + duration.plus(Duration.ofMinutes(durationInline.getMinutes())); + duration.plus(Duration.ofHours(durationInline.getHours())); + duration.plus(Duration.ofDays(durationInline.getDays())); + return duration; + } + + @Override + public TaskExecutor buildInstance() { + return new WaitExecutor(this); + } + } + + protected WaitExecutor(WaitExecutorBuilder builder) { + super(builder); + this.millisToWait = builder.millisToWait; + } + + @Override + protected CompletableFuture internalExecute( + WorkflowContext workflow, TaskContext taskContext) { + workflow.instance().status(WorkflowStatus.WAITING); + return new CompletableFuture() + .completeOnTimeout(taskContext.output(), millisToWait.toMillis(), TimeUnit.MILLISECONDS) + .thenApply( + node -> { + workflow.instance().status(WorkflowStatus.RUNNING); + return node; + }); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/DateTimeDescriptor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/DateTimeDescriptor.java new file mode 100644 index 00000000..898476f8 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/DateTimeDescriptor.java @@ -0,0 +1,45 @@ +/* + * 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.impl.expressions; + +import java.time.Instant; + +public class DateTimeDescriptor { + + private final Instant instant; + + public static DateTimeDescriptor from(Instant instant) { + return new DateTimeDescriptor(instant); + } + + private DateTimeDescriptor(Instant instant) { + this.instant = instant; + } + + public String getIso8601() { + return instant.toString(); + } + + public Epoch getEpoch() { + return Epoch.of(instant); + } + + public static record Epoch(long seconds, long milliseconds) { + public static Epoch of(Instant instant) { + return new Epoch(instant.getEpochSecond(), instant.toEpochMilli()); + } + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/Expression.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/Expression.java new file mode 100644 index 00000000..f2e91ace --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/Expression.java @@ -0,0 +1,24 @@ +/* + * 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.impl.expressions; + +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; + +public interface Expression { + WorkflowModel eval(WorkflowContext workflowContext, TaskContext context, WorkflowModel model); +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ExpressionFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ExpressionFactory.java new file mode 100644 index 00000000..28039cc7 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ExpressionFactory.java @@ -0,0 +1,39 @@ +/* + * 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.impl.expressions; + +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.impl.ServicePriority; +import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowModelFactory; +import java.util.Optional; + +public interface ExpressionFactory extends ServicePriority { + /** + * @throws ExpressionValidationException + * @param expression + * @return + */ + Expression buildExpression(String expression); + + WorkflowFilter buildFilter(String expr, Object value); + + WorkflowModelFactory modelFactory(); + + default Optional buildIfFilter(TaskBase task) { + return task.getIf() != null ? Optional.of(buildFilter(task.getIf(), null)) : Optional.empty(); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ExpressionUtils.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ExpressionUtils.java new file mode 100644 index 00000000..83f6fe1c --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ExpressionUtils.java @@ -0,0 +1,64 @@ +/* + * 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.impl.expressions; + +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import java.util.Map; + +public class ExpressionUtils { + + private static final String EXPR_PREFIX = "${"; + private static final String EXPR_SUFFIX = "}"; + + private ExpressionUtils() {} + + public static Map buildExpressionMap( + Map origMap, ExpressionFactory factory) { + return new ProxyMap(origMap, o -> isExpr(o) ? factory.buildExpression(o.toString()) : o); + } + + public static Map evaluateExpressionMap( + Map origMap, WorkflowContext workflow, TaskContext task, WorkflowModel n) { + return new ProxyMap( + origMap, o -> o instanceof Expression ? ((Expression) o).eval(workflow, task, n) : o); + } + + public static Object buildExpressionObject(Object obj, ExpressionFactory factory) { + return obj instanceof Map map ? ExpressionUtils.buildExpressionMap(map, factory) : obj; + } + + public static boolean isExpr(Object expr) { + return expr instanceof String && ((String) expr).startsWith(EXPR_PREFIX); + } + + public static String trimExpr(String expr) { + expr = expr.trim(); + if (expr.startsWith(EXPR_PREFIX)) { + expr = trimExpr(expr, EXPR_PREFIX, EXPR_SUFFIX); + } + return expr.trim(); + } + + private static String trimExpr(String expr, String prefix, String suffix) { + expr = expr.substring(prefix.length()); + if (expr.endsWith(suffix)) { + expr = expr.substring(0, expr.length() - suffix.length()); + } + return expr; + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ExpressionValidationException.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ExpressionValidationException.java new file mode 100644 index 00000000..8dc1b4f2 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ExpressionValidationException.java @@ -0,0 +1,29 @@ +/* + * 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.impl.expressions; + +public class ExpressionValidationException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public ExpressionValidationException(String message) { + super(message); + } + + public ExpressionValidationException(String message, Throwable ex) { + super(message, ex); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ObjectExpressionFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ObjectExpressionFactory.java new file mode 100644 index 00000000..f96894cc --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ObjectExpressionFactory.java @@ -0,0 +1,37 @@ +/* + * 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.impl.expressions; + +import io.serverlessworkflow.impl.WorkflowFilter; +import java.util.Map; + +public abstract class ObjectExpressionFactory implements ExpressionFactory { + + @Override + public WorkflowFilter buildFilter(String str, Object object) { + if (str != null) { + assert str != null; + Expression expression = buildExpression(str); + return expression::eval; + } else if (object != null) { + Object exprObj = ExpressionUtils.buildExpressionObject(object, this); + return exprObj instanceof Map map + ? (w, t, n) -> modelFactory().from(ExpressionUtils.evaluateExpressionMap(map, w, t, n)) + : (w, t, n) -> modelFactory().fromAny(object); + } + throw new IllegalArgumentException("Both object and str are null"); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ProxyMap.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ProxyMap.java new file mode 100644 index 00000000..bf4464b2 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/ProxyMap.java @@ -0,0 +1,278 @@ +/* + * 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.impl.expressions; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.function.UnaryOperator; + +public class ProxyMap implements Map { + + private final Map map; + private final UnaryOperator function; + + public ProxyMap(Map map, UnaryOperator function) { + this.map = map; + this.function = function; + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @Override + public Object get(Object key) { + return processValue(map.get(key)); + } + + @Override + public Object put(String key, Object value) { + return map.put(key, processValue(value)); + } + + @Override + public Object remove(Object key) { + return map.remove(key); + } + + @Override + public void putAll(Map m) { + map.putAll(m); + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public Set keySet() { + return map.keySet(); + } + + @Override + public Collection values() { + return new ProxyCollection(map.values()); + } + + @Override + public Set> entrySet() { + return new ProxyEntrySet(map.entrySet()); + } + + private abstract class AbstractProxyCollection { + + protected Collection values; + + protected AbstractProxyCollection(Collection values) { + this.values = values; + } + + public int size() { + return values.size(); + } + + public boolean isEmpty() { + return values.isEmpty(); + } + + public boolean contains(Object o) { + return values.contains(o); + } + + public boolean remove(Object o) { + return values.remove(o); + } + + public boolean containsAll(Collection c) { + return values.containsAll(c); + } + + public boolean retainAll(Collection c) { + return values.retainAll(c); + } + + public boolean removeAll(Collection c) { + return values.removeAll(c); + } + + public void clear() { + values.clear(); + } + + public boolean addAll(Collection c) { + return values.addAll(c); + } + + public boolean add(T e) { + return values.add(e); + } + } + + private class ProxyEntrySet extends AbstractProxyCollection> + implements Set> { + + public ProxyEntrySet(Set> entrySet) { + super(entrySet); + } + + @Override + public Iterator> iterator() { + return new ProxyEntryIterator(values.iterator()); + } + + @Override + public Object[] toArray() { + return processEntries(values.toArray()); + } + + @Override + public T[] toArray(T[] a) { + return processEntries(values.toArray(a)); + } + + private T[] processEntries(T[] array) { + for (int i = 0; i < array.length; i++) { + array[i] = (T) new ProxyEntry((Entry) array[i]); + } + return array; + } + } + + private class ProxyCollection extends AbstractProxyCollection + implements Collection { + + public ProxyCollection(Collection values) { + super(values); + } + + @Override + public Iterator iterator() { + return new ProxyIterator(values.iterator()); + } + + @Override + public Object[] toArray() { + return processArray(values.toArray()); + } + + @Override + public T[] toArray(T[] a) { + return processArray(values.toArray(a)); + } + + private S[] processArray(S[] array) { + for (int i = 0; i < array.length; i++) { + array[i] = (S) processValue(array[i]); + } + return array; + } + } + + private class ProxyEntry implements Entry { + + private Entry entry; + + private ProxyEntry(Entry entry) { + this.entry = entry; + } + + @Override + public String getKey() { + return entry.getKey(); + } + + @Override + public Object getValue() { + return processValue(entry.getValue()); + } + + @Override + public Object setValue(Object value) { + return entry.setValue(value); + } + } + + private class ProxyIterator implements Iterator { + + private Iterator iter; + + public ProxyIterator(Iterator iter) { + this.iter = iter; + } + + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + public Object next() { + return processValue(iter.next()); + } + + @Override + public void remove() { + iter.remove(); + } + } + + private class ProxyEntryIterator implements Iterator> { + + private Iterator> iter; + + public ProxyEntryIterator(Iterator> iter) { + this.iter = iter; + } + + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + public Entry next() { + return new ProxyEntry(iter.next()); + } + + @Override + public void remove() { + iter.remove(); + } + } + + private Object processValue(T obj) { + return function.apply(obj); + } +} diff --git a/api/src/main/java/io/serverlessworkflow/api/interfaces/WorkflowPropertySource.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/RuntimeDescriptor.java similarity index 73% rename from api/src/main/java/io/serverlessworkflow/api/interfaces/WorkflowPropertySource.java rename to impl/core/src/main/java/io/serverlessworkflow/impl/expressions/RuntimeDescriptor.java index d3cb4371..66286632 100644 --- a/api/src/main/java/io/serverlessworkflow/api/interfaces/WorkflowPropertySource.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/RuntimeDescriptor.java @@ -1,26 +1,21 @@ /* * 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.interfaces; - -import java.util.Properties; -public interface WorkflowPropertySource { +package io.serverlessworkflow.impl.expressions; - Properties getPropertySource(); +import java.util.Map; - void setPropertySource(Properties source); -} \ No newline at end of file +public record RuntimeDescriptor(String name, String version, Map metadata) {} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/TaskDescriptor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/TaskDescriptor.java new file mode 100644 index 00000000..a4919002 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/TaskDescriptor.java @@ -0,0 +1,39 @@ +/* + * 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.impl.expressions; + +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowModel; + +public record TaskDescriptor( + String name, + String reference, + TaskBase definition, + WorkflowModel rawInput, + WorkflowModel rawOutput, + DateTimeDescriptor startedAt) { + + public static TaskDescriptor of(TaskContext context) { + return new TaskDescriptor( + context.taskName(), + context.position().jsonPointer(), + context.task(), + context.rawInput(), + context.rawOutput(), + DateTimeDescriptor.from(context.startedAt())); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/WorkflowDescriptor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/WorkflowDescriptor.java new file mode 100644 index 00000000..cf87cfbb --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/expressions/WorkflowDescriptor.java @@ -0,0 +1,33 @@ +/* + * 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.impl.expressions; + +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; + +public record WorkflowDescriptor( + String id, Workflow definition, WorkflowModel input, DateTimeDescriptor startedAt) { + + public static WorkflowDescriptor of(WorkflowContext context) { + return new WorkflowDescriptor( + context.instance().id(), + context.definition().workflow(), + context.instance().input(), + DateTimeDescriptor.from(context.instance().startedAt())); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ClasspathResource.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ClasspathResource.java new file mode 100644 index 00000000..dad39237 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ClasspathResource.java @@ -0,0 +1,37 @@ +/* + * 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.impl.resources; + +import java.io.InputStream; + +public class ClasspathResource implements StaticResource { + + private String path; + + public ClasspathResource(String path) { + this.path = path; + } + + @Override + public InputStream open() { + return Thread.currentThread().getContextClassLoader().getResourceAsStream(path); + } + + @Override + public String name() { + return path; + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DefaultResourceLoader.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DefaultResourceLoader.java new file mode 100644 index 00000000..a23a9224 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DefaultResourceLoader.java @@ -0,0 +1,105 @@ +/* + * 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.impl.resources; + +import io.serverlessworkflow.api.types.Endpoint; +import io.serverlessworkflow.api.types.EndpointUri; +import io.serverlessworkflow.api.types.ExternalResource; +import io.serverlessworkflow.api.types.UriTemplate; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.expressions.ExpressionFactory; +import java.net.MalformedURLException; +import java.net.URI; +import java.nio.file.Path; +import java.util.Optional; + +public class DefaultResourceLoader implements ResourceLoader { + + private final Optional workflowPath; + + protected DefaultResourceLoader(Path workflowPath) { + this.workflowPath = Optional.ofNullable(workflowPath); + } + + @Override + public StaticResource loadStatic(ExternalResource resource) { + return processEndpoint(resource.getEndpoint()); + } + + @Override + public DynamicResource loadDynamic( + WorkflowContext workflow, ExternalResource resource, ExpressionFactory factory) { + throw new UnsupportedOperationException("Dynamic loading of resources is not suppported"); + } + + private StaticResource buildFromString(String uri) { + return fileResource(uri); + } + + private StaticResource fileResource(String pathStr) { + Path path = Path.of(pathStr); + if (path.isAbsolute()) { + return new FileResource(path); + } else { + return workflowPath + .map(p -> new FileResource(p.resolve(path))) + .orElseGet(() -> new ClasspathResource(pathStr)); + } + } + + private StaticResource buildFromURI(URI uri) { + String scheme = uri.getScheme(); + if (scheme == null || scheme.equalsIgnoreCase("file")) { + return fileResource(uri.getPath()); + } else if (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")) { + try { + return new HttpResource(uri.toURL()); + } catch (MalformedURLException e) { + throw new IllegalArgumentException(e); + } + } else { + throw new UnsupportedOperationException("Unsupported scheme " + scheme); + } + } + + private StaticResource processEndpoint(Endpoint endpoint) { + if (endpoint.getEndpointConfiguration() != null) { + EndpointUri uri = endpoint.getEndpointConfiguration().getUri(); + if (uri.getLiteralEndpointURI() != null) { + return getURI(uri.getLiteralEndpointURI()); + } else if (uri.getExpressionEndpointURI() != null) { + throw new UnsupportedOperationException( + "Expression not supported for loading a static resource"); + } + } else if (endpoint.getRuntimeExpression() != null) { + throw new UnsupportedOperationException( + "Expression not supported for loading a static resource"); + } else if (endpoint.getUriTemplate() != null) { + return getURI(endpoint.getUriTemplate()); + } + throw new IllegalArgumentException("Invalid endpoint definition " + endpoint); + } + + private StaticResource getURI(UriTemplate template) { + if (template.getLiteralUri() != null) { + return buildFromURI(template.getLiteralUri()); + } else if (template.getLiteralUriTemplate() != null) { + return buildFromString(template.getLiteralUriTemplate()); + } else { + throw new IllegalStateException("Invalid endpoint definition" + template); + } + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DefaultResourceLoaderFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DefaultResourceLoaderFactory.java new file mode 100644 index 00000000..60717e1d --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DefaultResourceLoaderFactory.java @@ -0,0 +1,34 @@ +/* + * 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.impl.resources; + +import java.nio.file.Path; + +public class DefaultResourceLoaderFactory implements ResourceLoaderFactory { + + public static final ResourceLoaderFactory get() { + return factory; + } + + private static final ResourceLoaderFactory factory = new DefaultResourceLoaderFactory(); + + private DefaultResourceLoaderFactory() {} + + @Override + public ResourceLoader getResourceLoader(Path path) { + return new DefaultResourceLoader(path); + } +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DynamicResource.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DynamicResource.java new file mode 100644 index 00000000..cd8b1780 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/DynamicResource.java @@ -0,0 +1,26 @@ +/* + * 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.impl.resources; + +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import java.io.InputStream; +import java.util.Optional; + +public interface DynamicResource { + InputStream open(WorkflowContext workflow, Optional task, WorkflowModel input); +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/FileResource.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/FileResource.java new file mode 100644 index 00000000..6358b6ce --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/FileResource.java @@ -0,0 +1,45 @@ +/* + * 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.impl.resources; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; + +class FileResource implements StaticResource { + + private Path path; + + public FileResource(Path path) { + this.path = path; + } + + @Override + public InputStream open() { + try { + return Files.newInputStream(path); + } catch (IOException io) { + throw new UncheckedIOException(io); + } + } + + @Override + public String name() { + return path.getFileName().toString(); + } +} diff --git a/api/src/main/java/io/serverlessworkflow/api/mapper/YamlObjectMapper.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/HttpResource.java similarity index 55% rename from api/src/main/java/io/serverlessworkflow/api/mapper/YamlObjectMapper.java rename to impl/core/src/main/java/io/serverlessworkflow/impl/resources/HttpResource.java index 98411283..906e5c83 100644 --- a/api/src/main/java/io/serverlessworkflow/api/mapper/YamlObjectMapper.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/HttpResource.java @@ -1,31 +1,43 @@ /* * 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; +package io.serverlessworkflow.impl.resources; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature; -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.net.URL; -public class YamlObjectMapper extends BaseObjectMapper { - public YamlObjectMapper() { - this(null); - } +public class HttpResource implements StaticResource { + + private URL url; - public YamlObjectMapper(WorkflowPropertySource context) { - super(new YAMLFactory().enable(Feature.MINIMIZE_QUOTES), context); + public HttpResource(URL url) { + this.url = url; + } + + @Override + public InputStream open() { + try { + return url.openStream(); + } catch (IOException e) { + throw new UncheckedIOException(e); } + } + + public String name() { + return url.getFile(); + } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ResourceLoader.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ResourceLoader.java new file mode 100644 index 00000000..346856bd --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ResourceLoader.java @@ -0,0 +1,28 @@ +/* + * 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.impl.resources; + +import io.serverlessworkflow.api.types.ExternalResource; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.expressions.ExpressionFactory; + +public interface ResourceLoader { + + StaticResource loadStatic(ExternalResource resource); + + DynamicResource loadDynamic( + WorkflowContext context, ExternalResource resource, ExpressionFactory factory); +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ResourceLoaderFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ResourceLoaderFactory.java new file mode 100644 index 00000000..c064672b --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/ResourceLoaderFactory.java @@ -0,0 +1,22 @@ +/* + * 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.impl.resources; + +import java.nio.file.Path; + +public interface ResourceLoaderFactory { + ResourceLoader getResourceLoader(Path path); +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/resources/StaticResource.java b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/StaticResource.java new file mode 100644 index 00000000..32443dfc --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/resources/StaticResource.java @@ -0,0 +1,24 @@ +/* + * 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.impl.resources; + +import java.io.InputStream; + +public interface StaticResource { + InputStream open(); + + String name(); +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/schema/SchemaValidator.java b/impl/core/src/main/java/io/serverlessworkflow/impl/schema/SchemaValidator.java new file mode 100644 index 00000000..fa66676b --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/schema/SchemaValidator.java @@ -0,0 +1,22 @@ +/* + * 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.impl.schema; + +import io.serverlessworkflow.impl.WorkflowModel; + +public interface SchemaValidator { + void validate(WorkflowModel node); +} diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/schema/SchemaValidatorFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/schema/SchemaValidatorFactory.java new file mode 100644 index 00000000..ff57ef5d --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/schema/SchemaValidatorFactory.java @@ -0,0 +1,26 @@ +/* + * 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.impl.schema; + +import io.serverlessworkflow.api.types.SchemaInline; +import io.serverlessworkflow.impl.ServicePriority; +import io.serverlessworkflow.impl.resources.StaticResource; + +public interface SchemaValidatorFactory extends ServicePriority { + SchemaValidator getValidator(SchemaInline inline); + + SchemaValidator getValidator(StaticResource resource); +} diff --git a/impl/http/pom.xml b/impl/http/pom.xml new file mode 100644 index 00000000..516669a3 --- /dev/null +++ b/impl/http/pom.xml @@ -0,0 +1,59 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-impl + 8.0.0-SNAPSHOT + + serverlessworkflow-impl-http + Serverless Workflow :: Impl :: HTTP + + + jakarta.ws.rs + jakarta.ws.rs-api + + + io.serverlessworkflow + serverlessworkflow-impl-core + + + org.glassfish.jersey.media + jersey-media-json-jackson + + + org.glassfish.jersey.core + jersey-client + + + io.serverlessworkflow + serverlessworkflow-api + test + + + io.serverlessworkflow + serverlessworkflow-impl-jackson + test + + + org.junit.jupiter + junit-jupiter-api + + + org.junit.jupiter + junit-jupiter-engine + + + org.junit.jupiter + junit-jupiter-params + + + org.assertj + assertj-core + + + ch.qos.logback + logback-classic + test + + + \ No newline at end of file diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/AuthProvider.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/AuthProvider.java new file mode 100644 index 00000000..7a902435 --- /dev/null +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/AuthProvider.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.impl.executors.http; + +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import jakarta.ws.rs.client.Invocation; + +@FunctionalInterface +interface AuthProvider { + Invocation.Builder build( + Invocation.Builder builder, WorkflowContext workflow, TaskContext task, WorkflowModel model); +} diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/AuthProviderFactory.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/AuthProviderFactory.java new file mode 100644 index 00000000..86322e1a --- /dev/null +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/AuthProviderFactory.java @@ -0,0 +1,82 @@ +/* + * 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.impl.executors.http; + +import io.serverlessworkflow.api.types.AuthenticationPolicyUnion; +import io.serverlessworkflow.api.types.EndpointConfiguration; +import io.serverlessworkflow.api.types.ReferenceableAuthenticationPolicy; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.WorkflowApplication; +import java.util.Optional; + +class AuthProviderFactory { + + private AuthProviderFactory() {} + + static final String AUTH_HEADER_NAME = "Authorization"; + + public static Optional getAuth( + WorkflowApplication app, Workflow workflow, EndpointConfiguration endpointConfiguration) { + if (endpointConfiguration == null) { + return Optional.empty(); + } + ReferenceableAuthenticationPolicy auth = endpointConfiguration.getAuthentication(); + if (auth == null) { + return Optional.empty(); + } + if (auth.getAuthenticationPolicyReference() != null) { + return buildFromReference(app, workflow, auth.getAuthenticationPolicyReference().getUse()); + } else if (auth.getAuthenticationPolicy() != null) { + return buildFromPolicy(app, workflow, auth.getAuthenticationPolicy()); + } + return Optional.empty(); + } + + private static Optional buildFromReference( + WorkflowApplication app, Workflow workflow, String use) { + return workflow.getUse().getAuthentications().getAdditionalProperties().entrySet().stream() + .filter(s -> s.getKey().equals(use)) + .findAny() + .flatMap(e -> buildFromPolicy(app, workflow, e.getValue())); + } + + private static Optional buildFromPolicy( + WorkflowApplication app, Workflow workflow, AuthenticationPolicyUnion authenticationPolicy) { + if (authenticationPolicy.getBasicAuthenticationPolicy() != null) { + return Optional.of( + new BasicAuthProvider( + app, workflow, authenticationPolicy.getBasicAuthenticationPolicy())); + } else if (authenticationPolicy.getBearerAuthenticationPolicy() != null) { + return Optional.of( + new BearerAuthProvider( + app, workflow, authenticationPolicy.getBearerAuthenticationPolicy())); + } else if (authenticationPolicy.getDigestAuthenticationPolicy() != null) { + return Optional.of( + new DigestAuthProvider( + app, workflow, authenticationPolicy.getDigestAuthenticationPolicy())); + } else if (authenticationPolicy.getOAuth2AuthenticationPolicy() != null) { + return Optional.of( + new OAuth2AuthProvider( + app, workflow, authenticationPolicy.getOAuth2AuthenticationPolicy())); + } else if (authenticationPolicy.getOpenIdConnectAuthenticationPolicy() != null) { + return Optional.of( + new OpenIdAuthProvider( + app, workflow, authenticationPolicy.getOpenIdConnectAuthenticationPolicy())); + } + + return Optional.empty(); + } +} diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/BasicAuthProvider.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/BasicAuthProvider.java new file mode 100644 index 00000000..a8bcfead --- /dev/null +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/BasicAuthProvider.java @@ -0,0 +1,67 @@ +/* + * 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.impl.executors.http; + +import io.serverlessworkflow.api.types.BasicAuthenticationPolicy; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.StringFilter; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowUtils; +import jakarta.ws.rs.client.Invocation.Builder; +import java.util.Base64; + +class BasicAuthProvider implements AuthProvider { + + private static final String BASIC_TOKEN = "Basic %s"; + private static final String USER_PASSWORD = "%s:%s"; + + private StringFilter userFilter; + private StringFilter passwordFilter; + + public BasicAuthProvider( + WorkflowApplication app, Workflow workflow, BasicAuthenticationPolicy authPolicy) { + if (authPolicy.getBasic().getBasicAuthenticationProperties() != null) { + userFilter = + WorkflowUtils.buildStringFilter( + app, authPolicy.getBasic().getBasicAuthenticationProperties().getUsername()); + passwordFilter = + WorkflowUtils.buildStringFilter( + app, authPolicy.getBasic().getBasicAuthenticationProperties().getPassword()); + } else if (authPolicy.getBasic().getBasicAuthenticationPolicySecret() != null) { + throw new UnsupportedOperationException("Secrets are still not supported"); + } + } + + @Override + public Builder build( + Builder builder, WorkflowContext workflow, TaskContext task, WorkflowModel model) { + builder.header( + AuthProviderFactory.AUTH_HEADER_NAME, + String.format( + BASIC_TOKEN, + Base64.getEncoder() + .encode( + String.format( + USER_PASSWORD, + userFilter.apply(workflow, task), + passwordFilter.apply(workflow, task)) + .getBytes()))); + return builder; + } +} diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/BearerAuthProvider.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/BearerAuthProvider.java new file mode 100644 index 00000000..a0df5b61 --- /dev/null +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/BearerAuthProvider.java @@ -0,0 +1,56 @@ +/* + * 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.impl.executors.http; + +import io.serverlessworkflow.api.types.BearerAuthenticationPolicy; +import io.serverlessworkflow.api.types.BearerAuthenticationPolicyConfiguration; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.StringFilter; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowUtils; +import jakarta.ws.rs.client.Invocation.Builder; + +class BearerAuthProvider implements AuthProvider { + + private static final String BEARER_TOKEN = "Bearer %s"; + + private StringFilter tokenFilter; + + public BearerAuthProvider( + WorkflowApplication app, + Workflow workflow, + BearerAuthenticationPolicy basicAuthenticationPolicy) { + BearerAuthenticationPolicyConfiguration config = basicAuthenticationPolicy.getBearer(); + if (config.getBearerAuthenticationProperties() != null) { + String token = config.getBearerAuthenticationProperties().getToken(); + tokenFilter = WorkflowUtils.buildStringFilter(app, token); + } else if (config.getBearerAuthenticationPolicySecret() != null) { + throw new UnsupportedOperationException("Secrets are still not supported"); + } + } + + @Override + public Builder build( + Builder builder, WorkflowContext workflow, TaskContext task, WorkflowModel model) { + builder.header( + AuthProviderFactory.AUTH_HEADER_NAME, + String.format(BEARER_TOKEN, tokenFilter.apply(workflow, task))); + return builder; + } +} diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/DigestAuthProvider.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/DigestAuthProvider.java new file mode 100644 index 00000000..27a961b9 --- /dev/null +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/DigestAuthProvider.java @@ -0,0 +1,39 @@ +/* + * 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.impl.executors.http; + +import io.serverlessworkflow.api.types.DigestAuthenticationPolicy; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import jakarta.ws.rs.client.Invocation.Builder; + +public class DigestAuthProvider implements AuthProvider { + + public DigestAuthProvider( + WorkflowApplication app, Workflow workflow, DigestAuthenticationPolicy authPolicy) { + throw new UnsupportedOperationException("Digest auth not supported yet"); + } + + @Override + public Builder build( + Builder builder, WorkflowContext workflow, TaskContext task, WorkflowModel model) { + // TODO Auto-generated method stub + return builder; + } +} diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java new file mode 100644 index 00000000..b66a9319 --- /dev/null +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpExecutor.java @@ -0,0 +1,220 @@ +/* + * 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.impl.executors.http; + +import io.serverlessworkflow.api.types.CallHTTP; +import io.serverlessworkflow.api.types.Endpoint; +import io.serverlessworkflow.api.types.EndpointUri; +import io.serverlessworkflow.api.types.HTTPArguments; +import io.serverlessworkflow.api.types.TaskBase; +import io.serverlessworkflow.api.types.UriTemplate; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowError; +import io.serverlessworkflow.impl.WorkflowException; +import io.serverlessworkflow.impl.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowUtils; +import io.serverlessworkflow.impl.executors.CallableTask; +import io.serverlessworkflow.impl.expressions.Expression; +import io.serverlessworkflow.impl.expressions.ExpressionFactory; +import io.serverlessworkflow.impl.resources.ResourceLoader; +import jakarta.ws.rs.HttpMethod; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Invocation.Builder; +import jakarta.ws.rs.client.WebTarget; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; + +public class HttpExecutor implements CallableTask { + + private static final Client client = ClientBuilder.newClient(); + + private TargetSupplier targetSupplier; + private Optional headersMap; + private Optional queryMap; + private Optional authProvider; + private RequestSupplier requestFunction; + private HttpModelConverter converter = new HttpModelConverter() {}; + + @FunctionalInterface + private interface TargetSupplier { + WebTarget apply(WorkflowContext workflow, TaskContext task, WorkflowModel node); + } + + @FunctionalInterface + private interface RequestSupplier { + WorkflowModel apply( + Builder request, WorkflowContext workflow, TaskContext task, WorkflowModel node); + } + + @Override + public void init( + CallHTTP task, + Workflow workflow, + WorkflowApplication application, + ResourceLoader resourceLoader) { + HTTPArguments httpArgs = task.getWith(); + + this.authProvider = + AuthProviderFactory.getAuth( + application, workflow, task.getWith().getEndpoint().getEndpointConfiguration()); + + this.targetSupplier = + getTargetSupplier(httpArgs.getEndpoint(), application.expressionFactory()); + this.headersMap = + httpArgs.getHeaders() != null + ? Optional.of( + WorkflowUtils.buildWorkflowFilter( + application, + httpArgs.getHeaders().getRuntimeExpression(), + httpArgs.getHeaders().getHTTPHeaders() != null + ? httpArgs.getHeaders().getHTTPHeaders().getAdditionalProperties() + : null)) + : Optional.empty(); + this.queryMap = + httpArgs.getQuery() != null + ? Optional.of( + WorkflowUtils.buildWorkflowFilter( + application, + httpArgs.getQuery().getRuntimeExpression(), + httpArgs.getQuery().getHTTPQuery() != null + ? httpArgs.getQuery().getHTTPQuery().getAdditionalProperties() + : null)) + : Optional.empty(); + switch (httpArgs.getMethod().toUpperCase()) { + case HttpMethod.POST: + WorkflowFilter bodyFilter = + WorkflowUtils.buildWorkflowFilter(application, null, httpArgs.getBody()); + this.requestFunction = + (request, w, context, node) -> + converter.toModel( + application.modelFactory(), + request.post( + converter.toEntity(bodyFilter.apply(w, context, node)), + node.objectClass())); + break; + case HttpMethod.GET: + default: + this.requestFunction = + (request, w, t, n) -> + converter.toModel(application.modelFactory(), request.get(n.objectClass())); + } + } + + private static class TargetQuerySupplier implements Supplier { + + private WebTarget target; + + public TargetQuerySupplier(WebTarget original) { + this.target = original; + } + + public void addQuery(String key, Object value) { + target = target.queryParam(key, value); + } + + public WebTarget get() { + return target; + } + } + + @Override + public CompletableFuture apply( + WorkflowContext workflow, TaskContext taskContext, WorkflowModel input) { + TargetQuerySupplier supplier = + new TargetQuerySupplier(targetSupplier.apply(workflow, taskContext, input)); + queryMap.ifPresent( + q -> + q.apply(workflow, taskContext, input) + .forEach((k, v) -> supplier.addQuery(k, v.asJavaObject()))); + Builder request = supplier.get().request(); + authProvider.ifPresent(auth -> auth.build(request, workflow, taskContext, input)); + headersMap.ifPresent( + h -> + h.apply(workflow, taskContext, input) + .forEach((k, v) -> request.header(k, v.asJavaObject()))); + return CompletableFuture.supplyAsync( + () -> { + try { + return requestFunction.apply(request, workflow, taskContext, input); + } catch (WebApplicationException exception) { + throw new WorkflowException( + WorkflowError.communication( + exception.getResponse().getStatus(), taskContext, exception) + .build()); + } + }, + workflow.definition().application().executorService()); + } + + @Override + public boolean accept(Class clazz) { + return clazz.equals(CallHTTP.class); + } + + private static TargetSupplier getTargetSupplier( + Endpoint endpoint, ExpressionFactory expressionFactory) { + if (endpoint.getEndpointConfiguration() != null) { + EndpointUri uri = endpoint.getEndpointConfiguration().getUri(); + if (uri.getLiteralEndpointURI() != null) { + return getURISupplier(uri.getLiteralEndpointURI()); + } else if (uri.getExpressionEndpointURI() != null) { + return new ExpressionURISupplier( + expressionFactory.buildExpression(uri.getExpressionEndpointURI())); + } + } else if (endpoint.getRuntimeExpression() != null) { + return new ExpressionURISupplier( + expressionFactory.buildExpression(endpoint.getRuntimeExpression())); + } else if (endpoint.getUriTemplate() != null) { + return getURISupplier(endpoint.getUriTemplate()); + } + throw new IllegalArgumentException("Invalid endpoint definition " + endpoint); + } + + private static TargetSupplier getURISupplier(UriTemplate template) { + if (template.getLiteralUri() != null) { + return (w, t, n) -> client.target(template.getLiteralUri()); + } else if (template.getLiteralUriTemplate() != null) { + return (w, t, n) -> + client.target(template.getLiteralUriTemplate()).resolveTemplates(n.asMap().orElseThrow()); + } + throw new IllegalArgumentException("Invalid uritemplate definition " + template); + } + + private static class ExpressionURISupplier implements TargetSupplier { + private Expression expr; + + public ExpressionURISupplier(Expression expr) { + this.expr = expr; + } + + @Override + public WebTarget apply(WorkflowContext workflow, TaskContext task, WorkflowModel node) { + return client.target( + expr.eval(workflow, task, node) + .asText() + .orElseThrow( + () -> + new IllegalArgumentException("Target expression requires a string result"))); + } + } +} diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpModelConverter.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpModelConverter.java new file mode 100644 index 00000000..161db2ec --- /dev/null +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/HttpModelConverter.java @@ -0,0 +1,31 @@ +/* + * 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.impl.executors.http; + +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowModelFactory; +import jakarta.ws.rs.client.Entity; + +public interface HttpModelConverter { + + default WorkflowModel toModel(WorkflowModelFactory factory, Object entity) { + return factory.fromAny(entity); + } + + default Entity toEntity(WorkflowModel model) { + return Entity.json(model.asIs()); + } +} diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/OAuth2AuthProvider.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/OAuth2AuthProvider.java new file mode 100644 index 00000000..9a558be2 --- /dev/null +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/OAuth2AuthProvider.java @@ -0,0 +1,39 @@ +/* + * 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.impl.executors.http; + +import io.serverlessworkflow.api.types.OAuth2AuthenticationPolicy; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import jakarta.ws.rs.client.Invocation.Builder; + +public class OAuth2AuthProvider implements AuthProvider { + + public OAuth2AuthProvider( + WorkflowApplication app, Workflow workflow, OAuth2AuthenticationPolicy authPolicy) { + throw new UnsupportedOperationException("Oauth2 auth not supported yet"); + } + + @Override + public Builder build( + Builder builder, WorkflowContext workflow, TaskContext task, WorkflowModel model) { + // TODO Auto-generated method stub + return builder; + } +} diff --git a/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/OpenIdAuthProvider.java b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/OpenIdAuthProvider.java new file mode 100644 index 00000000..fcbc7273 --- /dev/null +++ b/impl/http/src/main/java/io/serverlessworkflow/impl/executors/http/OpenIdAuthProvider.java @@ -0,0 +1,39 @@ +/* + * 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.impl.executors.http; + +import io.serverlessworkflow.api.types.OpenIdConnectAuthenticationPolicy; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import jakarta.ws.rs.client.Invocation.Builder; + +public class OpenIdAuthProvider implements AuthProvider { + + public OpenIdAuthProvider( + WorkflowApplication app, Workflow workflow, OpenIdConnectAuthenticationPolicy authPolicy) { + throw new UnsupportedOperationException("OpenId auth not supported yet"); + } + + @Override + public Builder build( + Builder builder, WorkflowContext workflow, TaskContext task, WorkflowModel model) { + // TODO Auto-generated method stub + return builder; + } +} diff --git a/impl/http/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.CallableTask b/impl/http/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.CallableTask new file mode 100644 index 00000000..2a9aac2d --- /dev/null +++ b/impl/http/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.CallableTask @@ -0,0 +1 @@ +io.serverlessworkflow.impl.executors.http.HttpExecutor \ No newline at end of file diff --git a/impl/http/src/test/java/io/serverlessworkflow/impl/HTTPWorkflowDefinitionTest.java b/impl/http/src/test/java/io/serverlessworkflow/impl/HTTPWorkflowDefinitionTest.java new file mode 100644 index 00000000..fd1c575b --- /dev/null +++ b/impl/http/src/test/java/io/serverlessworkflow/impl/HTTPWorkflowDefinitionTest.java @@ -0,0 +1,103 @@ +/* + * 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.impl; + +import static io.serverlessworkflow.api.WorkflowReader.readWorkflowFromClasspath; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowableOfType; + +import java.io.IOException; +import java.util.Map; +import java.util.stream.Stream; +import org.assertj.core.api.Condition; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +public class HTTPWorkflowDefinitionTest { + + private static WorkflowApplication appl; + + @BeforeAll + static void init() { + appl = WorkflowApplication.builder().build(); + } + + @ParameterizedTest + @MethodSource("provideParameters") + void testWorkflowExecution(String fileName, Object input, Condition condition) + throws IOException { + assertThat( + appl.workflowDefinition(readWorkflowFromClasspath(fileName)) + .instance(input) + .start() + .join()) + .is(condition); + } + + @ParameterizedTest + @ValueSource( + strings = { + "call-http-query-parameters.yaml", + "call-http-query-parameters-external-schema.yaml" + }) + void testWrongSchema(String fileName) { + IllegalArgumentException exception = + catchThrowableOfType( + IllegalArgumentException.class, + () -> appl.workflowDefinition(readWorkflowFromClasspath(fileName)).instance(Map.of())); + assertThat(exception) + .isNotNull() + .hasMessageContaining("There are JsonSchema validation errors"); + } + + private static boolean httpCondition(WorkflowModel obj) { + Map map = obj.asMap().orElseThrow(); + return map.containsKey("photoUrls") || map.containsKey("petId"); + } + + private static Stream provideParameters() { + Map petInput = Map.of("petId", 10); + Map starTrekInput = Map.of("uid", "MOMA0000092393"); + Condition petCondition = + new Condition<>(HTTPWorkflowDefinitionTest::httpCondition, "callHttpCondition"); + Condition starTrekCondition = + new Condition<>( + o -> + ((Map) o.asMap().orElseThrow().get("movie")) + .get("title") + .equals("Star Trek"), + "StartTrek"); + return Stream.of( + Arguments.of("callGetHttp.yaml", petInput, petCondition), + Arguments.of( + "callGetHttp.yaml", + Map.of("petId", "-1"), + new Condition( + o -> o.asMap().orElseThrow().containsKey("petId"), "notFoundCondition")), + Arguments.of("call-http-endpoint-interpolation.yaml", petInput, petCondition), + Arguments.of("call-http-query-parameters.yaml", starTrekInput, starTrekCondition), + Arguments.of( + "call-http-query-parameters-external-schema.yaml", starTrekInput, starTrekCondition), + Arguments.of( + "callPostHttp.yaml", + Map.of("name", "Javierito", "surname", "Unknown"), + new Condition( + o -> o.asText().orElseThrow().equals("Javierito"), "CallHttpPostCondition"))); + } +} diff --git a/impl/http/src/test/resources/call-http-endpoint-interpolation.yaml b/impl/http/src/test/resources/call-http-endpoint-interpolation.yaml new file mode 100644 index 00000000..43ba4988 --- /dev/null +++ b/impl/http/src/test/resources/call-http-endpoint-interpolation.yaml @@ -0,0 +1,20 @@ +document: + dsl: 1.0.0-alpha5 + namespace: test + name: call-http-shorthand-endpoint + version: '0.1.0' +do: + - tryGetPet: + try: + - getPet: + call: http + with: + headers: + content-type: application/json + method: get + endpoint: ${ "https://petstore.swagger.io/v2/pet/\(.petId)" } + catch: + errors: + with: + type: https://serverlessworkflow.io/spec/1.0.0/errors/communication + status: 404 \ No newline at end of file diff --git a/impl/http/src/test/resources/call-http-query-parameters-external-schema.yaml b/impl/http/src/test/resources/call-http-query-parameters-external-schema.yaml new file mode 100644 index 00000000..467b3632 --- /dev/null +++ b/impl/http/src/test/resources/call-http-query-parameters-external-schema.yaml @@ -0,0 +1,18 @@ +document: + dsl: 1.0.0-alpha2 + namespace: test + name: http-query-params-schema + version: 1.0.0-alpha2 +input: + schema: + resource: + endpoint: schema/searchquery.yaml +do: + - searchStarTrekMovies: + call: http + with: + method: get + endpoint: https://stapi.co/api/v1/rest/movie + query: + uid: ${.uid} + diff --git a/impl/http/src/test/resources/call-http-query-parameters.yaml b/impl/http/src/test/resources/call-http-query-parameters.yaml new file mode 100644 index 00000000..b207d092 --- /dev/null +++ b/impl/http/src/test/resources/call-http-query-parameters.yaml @@ -0,0 +1,23 @@ +document: + dsl: 1.0.0-alpha2 + namespace: test + name: http-query-params + version: 1.0.0-alpha2 +input: + schema: + document: + type: object + required: + - uid + properties: + uid: + type: string +do: + - searchStarTrekMovies: + call: http + with: + method: get + endpoint: https://stapi.co/api/v1/rest/movie + query: + uid: ${.uid} + diff --git a/impl/http/src/test/resources/callGetHttp.yaml b/impl/http/src/test/resources/callGetHttp.yaml new file mode 100644 index 00000000..192b0bcd --- /dev/null +++ b/impl/http/src/test/resources/callGetHttp.yaml @@ -0,0 +1,21 @@ +document: + dsl: 1.0.0-alpha1 + namespace: test + name: http-call-with-response + version: 1.0.0 +do: + - tryGetPet: + try: + - getPet: + call: http + with: + headers: + content-type: application/json + method: get + endpoint: + uri: https://petstore.swagger.io/v2/pet/{petId} + catch: + errors: + with: + type: https://serverlessworkflow.io/spec/1.0.0/errors/communication + status: 404 \ No newline at end of file diff --git a/impl/http/src/test/resources/callPostHttp.yaml b/impl/http/src/test/resources/callPostHttp.yaml new file mode 100644 index 00000000..f12fec42 --- /dev/null +++ b/impl/http/src/test/resources/callPostHttp.yaml @@ -0,0 +1,17 @@ +document: + dsl: 1.0.0-alpha1 + namespace: test + name: http-call-with-response-output + version: 1.0.0 +do: + - postPet: + call: http + with: + method: post + endpoint: + uri: https://fakerestapi.azurewebsites.net/api/v1/Authors + body: + firstName: ${.name} + lastName: ${.surname } + output: + as: .firstName \ No newline at end of file diff --git a/impl/http/src/test/resources/schema/searchquery.yaml b/impl/http/src/test/resources/schema/searchquery.yaml new file mode 100644 index 00000000..26b8e8d2 --- /dev/null +++ b/impl/http/src/test/resources/schema/searchquery.yaml @@ -0,0 +1,6 @@ +type: object +required: + - uid +properties: + uid: + type: string \ No newline at end of file diff --git a/impl/jackson/pom.xml b/impl/jackson/pom.xml new file mode 100644 index 00000000..babc6904 --- /dev/null +++ b/impl/jackson/pom.xml @@ -0,0 +1,57 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-impl + 8.0.0-SNAPSHOT + + serverlessworkflow-impl-jackson + Serverless Workflow :: Impl :: HTTP + + + io.serverlessworkflow + serverlessworkflow-impl-core + + + io.serverlessworkflow + serverlessworkflow-api + + + io.cloudevents + cloudevents-json-jackson + + + com.networknt + json-schema-validator + + + net.thisptr + jackson-jq + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.assertj + assertj-core + test + + + ch.qos.logback + logback-classic + test + + + \ No newline at end of file diff --git a/impl/jackson/src/main/java/io/serverlessworkflow/impl/events/json/JacksonCloudEventUtils.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/events/json/JacksonCloudEventUtils.java new file mode 100644 index 00000000..10ea02be --- /dev/null +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/events/json/JacksonCloudEventUtils.java @@ -0,0 +1,78 @@ +/* + * 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.impl.events.json; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.cloudevents.CloudEvent; +import io.cloudevents.CloudEventData; +import io.cloudevents.jackson.JsonCloudEventData; +import io.serverlessworkflow.impl.jackson.JsonUtils; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Date; + +public class JacksonCloudEventUtils { + + public static JsonNode toJsonNode(CloudEvent event) { + ObjectNode result = JsonUtils.mapper().createObjectNode(); + if (event.getData() != null) { + result.set("data", toJsonNode(event.getData())); + } + if (event.getSubject() != null) { + result.put("subject", event.getSubject()); + } + if (event.getDataContentType() != null) { + result.put("datacontenttype", event.getDataContentType()); + } + result.put("id", event.getId()); + result.put("source", event.getSource().toString()); + result.put("type", event.getType()); + result.put("specversion", event.getSpecVersion().toString()); + if (event.getDataSchema() != null) { + result.put("dataschema", event.getDataSchema().toString()); + } + if (event.getTime() != null) { + result.put("time", event.getTime().toString()); + } + event + .getExtensionNames() + .forEach(n -> result.set(n, JsonUtils.fromValue(event.getExtension(n)))); + return result; + } + + public static OffsetDateTime toOffset(Date date) { + return date.toInstant().atOffset(ZoneOffset.UTC); + } + + public static JsonNode toJsonNode(CloudEventData data) { + if (data == null) { + return NullNode.instance; + } + try { + return data instanceof JsonCloudEventData + ? ((JsonCloudEventData) data).getNode() + : JsonUtils.mapper().readTree(data.toBytes()); + } catch (IOException io) { + throw new UncheckedIOException(io); + } + } + + private JacksonCloudEventUtils() {} +} diff --git a/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JQExpression.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JQExpression.java new file mode 100644 index 00000000..f7fd2504 --- /dev/null +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JQExpression.java @@ -0,0 +1,108 @@ +/* + * 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.impl.expressions.jq; + +import static io.serverlessworkflow.impl.jackson.JsonUtils.modelToJson; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowModelFactory; +import io.serverlessworkflow.impl.expressions.Expression; +import io.serverlessworkflow.impl.expressions.TaskDescriptor; +import io.serverlessworkflow.impl.expressions.WorkflowDescriptor; +import io.serverlessworkflow.impl.jackson.JsonUtils; +import java.util.function.Supplier; +import net.thisptr.jackson.jq.Output; +import net.thisptr.jackson.jq.Scope; +import net.thisptr.jackson.jq.Version; +import net.thisptr.jackson.jq.exception.JsonQueryException; +import net.thisptr.jackson.jq.internal.javacc.ExpressionParser; + +public class JQExpression implements Expression { + + private final Supplier scope; + private final String expr; + private final net.thisptr.jackson.jq.Expression internalExpr; + private final WorkflowModelFactory modelFactory; + + public JQExpression( + Supplier scope, String expr, Version version, WorkflowModelFactory modelFactory) + throws JsonQueryException { + this.expr = expr; + this.scope = scope; + this.internalExpr = ExpressionParser.compile(expr, version); + this.modelFactory = modelFactory; + } + + @Override + public WorkflowModel eval(WorkflowContext workflow, TaskContext task, WorkflowModel model) { + JsonNodeOutput output = new JsonNodeOutput(); + JsonNode node = modelToJson(model); + try { + internalExpr.apply(createScope(workflow, task), node, output); + return modelFactory.fromAny(output.getResult()); + } catch (JsonQueryException e) { + throw new IllegalArgumentException( + "Unable to evaluate content " + node + " using expr " + expr, e); + } + } + + private static class JsonNodeOutput implements Output { + private JsonNode result; + private boolean arrayCreated; + + @Override + public void emit(JsonNode out) throws JsonQueryException { + if (this.result == null) { + this.result = out; + } else if (!arrayCreated) { + ArrayNode newNode = JsonUtils.mapper().createArrayNode(); + newNode.add(this.result).add(out); + this.result = newNode; + arrayCreated = true; + } else { + ((ArrayNode) this.result).add(out); + } + } + + public JsonNode getResult() { + return result; + } + } + + private Scope createScope(WorkflowContext workflow, TaskContext task) { + Scope childScope = Scope.newChildScope(scope.get()); + if (task != null) { + childScope.setValue("input", modelToJson(task.input())); + childScope.setValue("output", modelToJson(task.output())); + childScope.setValue("task", () -> JsonUtils.fromValue(TaskDescriptor.of(task))); + task.variables().forEach((k, v) -> childScope.setValue(k, JsonUtils.fromValue(v))); + } + if (workflow != null) { + childScope.setValue("context", modelToJson(workflow.context())); + childScope.setValue( + "runtime", + () -> + JsonUtils.fromValue( + workflow.definition().application().runtimeDescriptorFactory().get())); + childScope.setValue("workflow", () -> JsonUtils.fromValue(WorkflowDescriptor.of(workflow))); + } + return childScope; + } +} diff --git a/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JQExpressionFactory.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JQExpressionFactory.java new file mode 100644 index 00000000..2909eb7b --- /dev/null +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JQExpressionFactory.java @@ -0,0 +1,64 @@ +/* + * 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.impl.expressions.jq; + +import io.serverlessworkflow.impl.WorkflowModelFactory; +import io.serverlessworkflow.impl.expressions.Expression; +import io.serverlessworkflow.impl.expressions.ExpressionUtils; +import io.serverlessworkflow.impl.expressions.ObjectExpressionFactory; +import java.util.function.Supplier; +import net.thisptr.jackson.jq.BuiltinFunctionLoader; +import net.thisptr.jackson.jq.Scope; +import net.thisptr.jackson.jq.Versions; +import net.thisptr.jackson.jq.exception.JsonQueryException; + +public class JQExpressionFactory extends ObjectExpressionFactory { + + private WorkflowModelFactory modelFactory = new JacksonModelFactory(); + + private static Supplier scopeSupplier = new DefaultScopeSupplier(); + + private static class DefaultScopeSupplier implements Supplier { + private static class DefaultScope { + private static Scope scope; + + static { + scope = Scope.newEmptyScope(); + BuiltinFunctionLoader.getInstance().loadFunctions(Versions.JQ_1_6, scope); + } + } + + @Override + public Scope get() { + return DefaultScope.scope; + } + } + + @Override + public Expression buildExpression(String expression) { + try { + return new JQExpression( + scopeSupplier, ExpressionUtils.trimExpr(expression), Versions.JQ_1_6, modelFactory); + } catch (JsonQueryException e) { + throw new IllegalArgumentException(e); + } + } + + @Override + public WorkflowModelFactory modelFactory() { + return modelFactory; + } +} diff --git a/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModel.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModel.java new file mode 100644 index 00000000..dc71fcb2 --- /dev/null +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModel.java @@ -0,0 +1,125 @@ +/* + * 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.impl.expressions.jq; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.BooleanNode; +import com.fasterxml.jackson.databind.node.NullNode; +import io.cloudevents.CloudEventData; +import io.cloudevents.jackson.JsonCloudEventData; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.jackson.JsonUtils; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiConsumer; + +@JsonSerialize(using = JacksonModelSerializer.class) +public class JacksonModel implements WorkflowModel { + + protected JsonNode node; + + public static final JacksonModel TRUE = new JacksonModel(BooleanNode.TRUE); + public static final JacksonModel FALSE = new JacksonModel(BooleanNode.FALSE); + public static final JacksonModel NULL = new JacksonModel(NullNode.instance); + + JacksonModel(JsonNode node) { + this.node = node; + } + + @Override + public void forEach(BiConsumer consumer) { + node.forEachEntry((k, v) -> consumer.accept(k, new JacksonModel(v))); + } + + @Override + public Optional asBoolean() { + return node.isBoolean() ? Optional.of(node.asBoolean()) : Optional.empty(); + } + + @Override + public Collection asCollection() { + return node.isArray() ? new JacksonModelCollection((ArrayNode) node) : Collections.emptyList(); + } + + @Override + public Optional asText() { + return node.isTextual() ? Optional.of(node.asText()) : Optional.empty(); + } + + @Override + public Optional asDate() { + if (node.isTextual()) { + return Optional.of(OffsetDateTime.parse(node.asText())); + } else if (node.isNumber()) { + return Optional.of( + OffsetDateTime.ofInstant(Instant.ofEpochMilli(node.asLong()), ZoneOffset.UTC)); + } else { + return Optional.empty(); + } + } + + @Override + public Optional asNumber() { + return node.isNumber() ? Optional.of(node.asLong()) : Optional.empty(); + } + + @Override + public Optional asCloudEventData() { + return node.isObject() ? Optional.of(JsonCloudEventData.wrap(node)) : Optional.empty(); + } + + @Override + public Optional as(Class clazz) { + return clazz.isAssignableFrom(node.getClass()) + ? Optional.of(clazz.cast(node)) + : Optional.of(JsonUtils.convertValue(node, clazz)); + } + + @Override + public String toString() { + return node.toPrettyString(); + } + + @Override + public Optional> asMap() { + // TODO use generic to avoid warning + return node.isObject() + ? Optional.of(JsonUtils.convertValue(node, Map.class)) + : Optional.empty(); + } + + @Override + public Object asJavaObject() { + return JsonUtils.toJavaValue(node); + } + + @Override + public Object asIs() { + return node; + } + + @Override + public Class objectClass() { + return node.getClass(); + } +} diff --git a/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModelCollection.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModelCollection.java new file mode 100644 index 00000000..70dee439 --- /dev/null +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModelCollection.java @@ -0,0 +1,157 @@ +/* + * 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.impl.expressions.jq; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowModelCollection; +import io.serverlessworkflow.impl.jackson.JsonUtils; +import java.util.Collection; +import java.util.Iterator; +import java.util.Optional; + +public class JacksonModelCollection implements WorkflowModelCollection { + + private ArrayNode node; + + JacksonModelCollection() { + this.node = JsonUtils.array(); + } + + JacksonModelCollection(ArrayNode node) { + this.node = node; + } + + @Override + public Optional as(Class clazz) { + return clazz.isAssignableFrom(ArrayNode.class) + ? Optional.of(clazz.cast(node)) + : Optional.empty(); + } + + @Override + public int size() { + return node.size(); + } + + @Override + public boolean isEmpty() { + return node.isEmpty(); + } + + @Override + public boolean contains(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator iterator() { + return new WrapperIterator(node.iterator()); + } + + private class WrapperIterator implements Iterator { + + private Iterator iterator; + + public WrapperIterator(Iterator iterator) { + this.iterator = iterator; + } + + @Override + public boolean hasNext() { + + return iterator.hasNext(); + } + + @Override + public WorkflowModel next() { + return new JacksonModel(iterator.next()); + } + } + + @Override + public Object[] toArray() { + throw new UnsupportedOperationException(); + } + + @Override + public T[] toArray(T[] a) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean add(WorkflowModel e) { + node.add( + e.as(JsonNode.class).orElseThrow(() -> new IllegalArgumentException("Not a json node"))); + return true; + } + + @Override + public boolean remove(Object o) { + int size = node.size(); + node.removeIf(i -> i.equals(o)); + return node.size() < size; + } + + @Override + public boolean containsAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection c) { + c.forEach(this::add); + return true; + } + + @Override + public boolean removeAll(Collection c) { + int size = node.size(); + c.forEach(o -> node.removeIf(i -> i.equals(o))); + return node.size() < size; + } + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + node.removeAll(); + } + + @Override + public String toString() { + return node.toPrettyString(); + } + + @Override + public Object asJavaObject() { + return JsonUtils.toJavaValue(node); + } + + @Override + public Object asIs() { + return node; + } + + @Override + public Class objectClass() { + return ArrayNode.class; + } +} diff --git a/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModelFactory.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModelFactory.java new file mode 100644 index 00000000..866c3264 --- /dev/null +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModelFactory.java @@ -0,0 +1,125 @@ +/* + * 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.impl.expressions.jq; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.DoubleNode; +import com.fasterxml.jackson.databind.node.FloatNode; +import com.fasterxml.jackson.databind.node.IntNode; +import com.fasterxml.jackson.databind.node.LongNode; +import com.fasterxml.jackson.databind.node.ShortNode; +import com.fasterxml.jackson.databind.node.TextNode; +import io.cloudevents.CloudEvent; +import io.cloudevents.CloudEventData; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowModelCollection; +import io.serverlessworkflow.impl.WorkflowModelFactory; +import io.serverlessworkflow.impl.events.json.JacksonCloudEventUtils; +import io.serverlessworkflow.impl.jackson.JsonUtils; +import java.math.BigDecimal; +import java.time.OffsetDateTime; +import java.util.Map; + +public class JacksonModelFactory implements WorkflowModelFactory { + + @Override + public WorkflowModel combine(Map workflowVariables) { + return new JacksonModelCollection( + workflowVariables.entrySet().stream() + .map( + e -> + JsonUtils.object() + .set( + e.getKey(), + e.getValue() + .as(JsonNode.class) + .orElseThrow( + () -> + new IllegalArgumentException( + "Model cannot be converted ")))) + .collect(JsonUtils.arrayNodeCollector())); + } + + @Override + public WorkflowModelCollection createCollection() { + return new JacksonModelCollection(); + } + + @Override + public WorkflowModel from(boolean value) { + return value ? JacksonModel.TRUE : JacksonModel.FALSE; + } + + @Override + public WorkflowModel from(Number number) { + if (number instanceof Double value) { + return new JacksonModel(new DoubleNode(value)); + } else if (number instanceof BigDecimal) { + return new JacksonModel(new DoubleNode(number.doubleValue())); + } else if (number instanceof Float value) { + return new JacksonModel(new FloatNode(value)); + } else if (number instanceof Long value) { + return new JacksonModel(new LongNode(value)); + } else if (number instanceof Integer value) { + return new JacksonModel(new IntNode(value)); + } else if (number instanceof Short value) { + return new JacksonModel(new ShortNode(value)); + } else if (number instanceof Byte value) { + return new JacksonModel(new ShortNode(value)); + } else { + return new JacksonModel(new LongNode(number.longValue())); + } + } + + @Override + public WorkflowModel from(String value) { + return new JacksonModel(new TextNode(value)); + } + + @Override + public WorkflowModel from(CloudEvent ce) { + return new JacksonModel(JacksonCloudEventUtils.toJsonNode(ce)); + } + + @Override + public WorkflowModel from(CloudEventData ce) { + return new JacksonModel(JacksonCloudEventUtils.toJsonNode(ce)); + } + + @Override + public WorkflowModel from(OffsetDateTime value) { + return new JacksonModel(JsonUtils.fromValue(new TextNode(value.toString()))); + } + + @Override + public WorkflowModel from(Map map) { + return new JacksonModel(JsonUtils.fromValue(map)); + } + + @Override + public WorkflowModel fromAny(Object obj) { + if (obj instanceof JsonNode node) { + return new JacksonModel(node); + } else { + return WorkflowModelFactory.super.fromAny(obj); + } + } + + @Override + public WorkflowModel fromNull() { + return JacksonModel.NULL; + } +} diff --git a/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModelSerializer.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModelSerializer.java new file mode 100644 index 00000000..7d4b07c9 --- /dev/null +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModelSerializer.java @@ -0,0 +1,36 @@ +/* + * 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.impl.expressions.jq; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import java.io.IOException; + +public class JacksonModelSerializer extends StdSerializer { + + private static final long serialVersionUID = 1L; + + protected JacksonModelSerializer() { + super(JacksonModel.class); + } + + @Override + public void serialize(JacksonModel value, JsonGenerator gen, SerializerProvider provider) + throws IOException { + gen.writeTree(value.node); + } +} diff --git a/impl/jackson/src/main/java/io/serverlessworkflow/impl/jackson/JsonUtils.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/jackson/JsonUtils.java new file mode 100644 index 00000000..04822457 --- /dev/null +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/jackson/JsonUtils.java @@ -0,0 +1,279 @@ +/* + * 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.impl.jackson; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.BigIntegerNode; +import com.fasterxml.jackson.databind.node.BinaryNode; +import com.fasterxml.jackson.databind.node.BooleanNode; +import com.fasterxml.jackson.databind.node.DecimalNode; +import com.fasterxml.jackson.databind.node.DoubleNode; +import com.fasterxml.jackson.databind.node.FloatNode; +import com.fasterxml.jackson.databind.node.IntNode; +import com.fasterxml.jackson.databind.node.LongNode; +import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.ShortNode; +import com.fasterxml.jackson.databind.node.TextNode; +import io.serverlessworkflow.impl.WorkflowModel; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; + +public class JsonUtils { + + private static ObjectMapper mapper = new ObjectMapper(); + + public static ObjectMapper mapper() { + return mapper; + } + + public static Collector arrayNodeCollector() { + return new Collector() { + @Override + public BiConsumer accumulator() { + return (arrayNode, item) -> arrayNode.add(item); + } + + @Override + public Set characteristics() { + return Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH)); + } + + @Override + public BinaryOperator combiner() { + return (r1, r2) -> { + r1.addAll(r2); + return r1; + }; + } + + @Override + public Function finisher() { + return arrayNode -> arrayNode; + } + + @Override + public Supplier supplier() { + return () -> mapper.createArrayNode(); + } + }; + } + + public static OffsetDateTime toOffsetDateTime(JsonNode node) { + return node.isTextual() + ? OffsetDateTime.parse(node.asText()) + : OffsetDateTime.ofInstant(Instant.ofEpochMilli(node.asLong()), ZoneOffset.UTC); + } + + /* + * Implementation note: + * Although we can use directly ObjectMapper.convertValue for implementing fromValue and toJavaValue methods, + * the performance gain of avoiding an intermediate buffer is so tempting that we cannot avoid it + */ + public static JsonNode fromValue(Object value) { + if (value == null) { + return NullNode.instance; + } else if (value instanceof JsonNode) { + return (JsonNode) value; + } else if (value instanceof Boolean) { + return BooleanNode.valueOf((Boolean) value); + } else if (value instanceof String) { + return fromString((String) value); + } else if (value instanceof Short) { + return new ShortNode((Short) value); + } else if (value instanceof Integer) { + return new IntNode((Integer) value); + } else if (value instanceof Long) { + return new LongNode((Long) value); + } else if (value instanceof Float) { + return new FloatNode((Float) value); + } else if (value instanceof Double) { + return new DoubleNode((Double) value); + } else if (value instanceof BigDecimal) { + return DecimalNode.valueOf((BigDecimal) value); + } else if (value instanceof BigInteger) { + return BigIntegerNode.valueOf((BigInteger) value); + } else if (value instanceof byte[]) { + return BinaryNode.valueOf((byte[]) value); + } else if (value instanceof Collection) { + return mapToArray((Collection) value); + } else if (value instanceof Map) { + return mapToNode((Map) value); + } else if (value instanceof WorkflowModel model) { + return modelToJson(model); + } else { + return mapper.convertValue(value, JsonNode.class); + } + } + + public static JsonNode modelToJson(WorkflowModel model) { + return model == null + ? NullNode.instance + : model + .as(JsonNode.class) + .orElseThrow( + () -> + new IllegalArgumentException( + "Unable to convert model " + model + " to JsonNode")); + } + + public static Object toJavaValue(Object object) { + return object instanceof JsonNode ? toJavaValue((JsonNode) object) : object; + } + + public static JsonNode fromString(String value) { + String trimmedValue = value.trim(); + if (trimmedValue.startsWith("{") && trimmedValue.endsWith("}")) { + try { + return mapper.readTree(trimmedValue); + } catch (IOException ex) { + // ignore and return test node + } + } + return new TextNode(value); + } + + private static Object toJavaValue(ObjectNode node) { + Map result = new HashMap<>(); + node.fields().forEachRemaining(iter -> result.put(iter.getKey(), toJavaValue(iter.getValue()))); + return result; + } + + private static Collection toJavaValue(ArrayNode node) { + Collection result = new ArrayList<>(); + for (JsonNode item : node) { + result.add(internalToJavaValue(item, JsonUtils::toJavaValue, JsonUtils::toJavaValue)); + } + return result; + } + + public static Object toJavaValue(JsonNode jsonNode) { + return internalToJavaValue(jsonNode, JsonUtils::toJavaValue, JsonUtils::toJavaValue); + } + + public static T convertValue(Object obj, Class returnType) { + if (returnType.isInstance(obj)) { + return returnType.cast(obj); + } else if (obj instanceof JsonNode) { + return convertValue((JsonNode) obj, returnType); + } else { + return mapper.convertValue(obj, returnType); + } + } + + public static T convertValue(JsonNode jsonNode, Class returnType) { + Object obj; + if (Boolean.class.isAssignableFrom(returnType)) { + obj = jsonNode.asBoolean(); + } else if (Integer.class.isAssignableFrom(returnType)) { + obj = jsonNode.asInt(); + } else if (Double.class.isAssignableFrom(returnType)) { + obj = jsonNode.asDouble(); + } else if (Long.class.isAssignableFrom(returnType)) { + obj = jsonNode.asLong(); + } else if (String.class.isAssignableFrom(returnType)) { + obj = jsonNode.asText(); + } else { + obj = mapper.convertValue(jsonNode, returnType); + } + return returnType.cast(obj); + } + + public static Object simpleToJavaValue(JsonNode jsonNode) { + return internalToJavaValue(jsonNode, node -> node, node -> node); + } + + private static Object internalToJavaValue( + JsonNode jsonNode, + Function objectFunction, + Function arrayFunction) { + if (jsonNode.isNull()) { + return null; + } else if (jsonNode.isTextual()) { + return jsonNode.asText(); + } else if (jsonNode.isBoolean()) { + return jsonNode.asBoolean(); + } else if (jsonNode.isInt()) { + return jsonNode.asInt(); + } else if (jsonNode.isDouble()) { + return jsonNode.asDouble(); + } else if (jsonNode.isNumber()) { + return jsonNode.numberValue(); + } else if (jsonNode.isArray()) { + return arrayFunction.apply((ArrayNode) jsonNode); + } else if (jsonNode.isObject()) { + return objectFunction.apply((ObjectNode) jsonNode); + } else { + return mapper.convertValue(jsonNode, Object.class); + } + } + + public static String toString(JsonNode node) throws JsonProcessingException { + return mapper.writeValueAsString(node); + } + + public static void addToNode(String name, Object value, ObjectNode dest) { + dest.set(name, fromValue(value)); + } + + private static ObjectNode mapToNode(Map value) { + ObjectNode objectNode = mapper.createObjectNode(); + for (Map.Entry entry : value.entrySet()) { + addToNode(entry.getKey(), entry.getValue(), objectNode); + } + return objectNode; + } + + private static ArrayNode mapToArray(Collection collection) { + return mapToArray(collection, mapper.createArrayNode()); + } + + private static ArrayNode mapToArray(Collection collection, ArrayNode arrayNode) { + for (Object item : collection) { + arrayNode.add(fromValue(item)); + } + return arrayNode; + } + + public static ObjectNode object() { + return mapper.createObjectNode(); + } + + public static ArrayNode array() { + return mapper.createArrayNode(); + } + + private JsonUtils() {} +} diff --git a/impl/jackson/src/main/java/io/serverlessworkflow/impl/jackson/MergeUtils.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/jackson/MergeUtils.java new file mode 100644 index 00000000..aca5d63d --- /dev/null +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/jackson/MergeUtils.java @@ -0,0 +1,107 @@ +/* + * 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.impl.jackson; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +public class MergeUtils { + /** + * Merge two JSON documents. + * + * @param src JsonNode to be merged + * @param target JsonNode to merge to + */ + public static JsonNode merge(JsonNode src, JsonNode target) { + return merge(src, target, false); + } + + public static JsonNode merge(JsonNode src, JsonNode target, boolean mergeArray) { + if (target == null + || target.isNull() + || target.isObject() && target.isEmpty() && src != null && !src.isNull()) { + return src; + } else if (target.isArray()) { + return mergeArray(src, (ArrayNode) target, mergeArray); + } else if (target.isObject()) { + return mergeObject(src, (ObjectNode) target, mergeArray); + } else { + if (src.isArray()) { + ArrayNode srcArray = (ArrayNode) src; + insert(srcArray, target, getExistingNodes(srcArray)); + } else if (src.isObject()) { + ((ObjectNode) src).set("_target", target); + } + return src; + } + } + + private static ObjectNode mergeObject(JsonNode src, ObjectNode target, boolean mergeArray) { + if (src.isObject()) { + Iterator> mergedIterator = src.fields(); + while (mergedIterator.hasNext()) { + Map.Entry entry = mergedIterator.next(); + JsonNode found = target.get(entry.getKey()); + target.set( + entry.getKey(), + found != null ? merge(entry.getValue(), found, mergeArray) : entry.getValue()); + } + } else if (!src.isNull()) { + target.set("response", src); + } + return target; + } + + private static JsonNode mergeArray(JsonNode src, ArrayNode target, boolean mergeArray) { + if (src != target) { + if (src.isArray()) { + if (mergeArray) { + ((ArrayNode) src).forEach(node -> add(target, node, getExistingNodes(target))); + } else { + return src; + } + } else { + add(target, src, getExistingNodes(target)); + } + } + return target; + } + + private static void add(ArrayNode array, JsonNode node, Set existingNodes) { + if (!existingNodes.contains(node)) { + array.add(node); + } + } + + private static void insert(ArrayNode array, JsonNode node, Set existingNodes) { + if (!existingNodes.contains(node)) { + array.insert(0, node); + } + } + + private static Set getExistingNodes(ArrayNode arrayNode) { + Set existingNodes = new HashSet<>(); + arrayNode.forEach(existingNodes::add); + return existingNodes; + } + + private MergeUtils() {} +} diff --git a/impl/jackson/src/main/java/io/serverlessworkflow/impl/schema/json/JsonSchemaValidator.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/schema/json/JsonSchemaValidator.java new file mode 100644 index 00000000..c4859ba9 --- /dev/null +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/schema/json/JsonSchemaValidator.java @@ -0,0 +1,50 @@ +/* + * 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.impl.schema.json; + +import com.fasterxml.jackson.databind.JsonNode; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.SpecVersion.VersionFlag; +import com.networknt.schema.ValidationMessage; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.schema.SchemaValidator; +import java.util.Set; + +public class JsonSchemaValidator implements SchemaValidator { + + private final JsonSchema schemaObject; + + public JsonSchemaValidator(JsonNode jsonNode) { + this.schemaObject = JsonSchemaFactory.getInstance(VersionFlag.V7).getSchema(jsonNode); + } + + @Override + public void validate(WorkflowModel node) { + Set report = + schemaObject.validate( + node.as(JsonNode.class) + .orElseThrow( + () -> + new IllegalArgumentException( + "Default schema validator requires WorkflowModel to support conversion to json node"))); + if (!report.isEmpty()) { + StringBuilder sb = new StringBuilder("There are JsonSchema validation errors:"); + report.forEach(m -> sb.append(System.lineSeparator()).append(m.getMessage())); + throw new IllegalArgumentException(sb.toString()); + } + } +} diff --git a/impl/jackson/src/main/java/io/serverlessworkflow/impl/schema/json/JsonSchemaValidatorFactory.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/schema/json/JsonSchemaValidatorFactory.java new file mode 100644 index 00000000..9b89e46c --- /dev/null +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/schema/json/JsonSchemaValidatorFactory.java @@ -0,0 +1,45 @@ +/* + * 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.impl.schema.json; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.serverlessworkflow.api.WorkflowFormat; +import io.serverlessworkflow.api.types.SchemaInline; +import io.serverlessworkflow.impl.jackson.JsonUtils; +import io.serverlessworkflow.impl.resources.StaticResource; +import io.serverlessworkflow.impl.schema.SchemaValidator; +import io.serverlessworkflow.impl.schema.SchemaValidatorFactory; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; + +public class JsonSchemaValidatorFactory implements SchemaValidatorFactory { + + @Override + public SchemaValidator getValidator(SchemaInline inline) { + return new JsonSchemaValidator(JsonUtils.fromValue(inline.getDocument())); + } + + @Override + public SchemaValidator getValidator(StaticResource resource) { + ObjectMapper mapper = WorkflowFormat.fromFileName(resource.name()).mapper(); + try (InputStream in = resource.open()) { + return new JsonSchemaValidator(mapper.readTree(in)); + } catch (IOException io) { + throw new UncheckedIOException(io); + } + } +} diff --git a/impl/jackson/src/main/resources/META-INF/services/io.serverlessworkflow.impl.expressions.ExpressionFactory b/impl/jackson/src/main/resources/META-INF/services/io.serverlessworkflow.impl.expressions.ExpressionFactory new file mode 100644 index 00000000..420f62ad --- /dev/null +++ b/impl/jackson/src/main/resources/META-INF/services/io.serverlessworkflow.impl.expressions.ExpressionFactory @@ -0,0 +1 @@ +io.serverlessworkflow.impl.expressions.jq.JQExpressionFactory \ No newline at end of file diff --git a/impl/jackson/src/main/resources/META-INF/services/io.serverlessworkflow.impl.schema.SchemaValidatorFactory b/impl/jackson/src/main/resources/META-INF/services/io.serverlessworkflow.impl.schema.SchemaValidatorFactory new file mode 100644 index 00000000..3100f8fa --- /dev/null +++ b/impl/jackson/src/main/resources/META-INF/services/io.serverlessworkflow.impl.schema.SchemaValidatorFactory @@ -0,0 +1 @@ +io.serverlessworkflow.impl.schema.json.JsonSchemaValidatorFactory \ No newline at end of file diff --git a/impl/jackson/src/test/java/io/serverlessworkflow/impl/DateTimeDescriptorTest.java b/impl/jackson/src/test/java/io/serverlessworkflow/impl/DateTimeDescriptorTest.java new file mode 100644 index 00000000..add57b92 --- /dev/null +++ b/impl/jackson/src/test/java/io/serverlessworkflow/impl/DateTimeDescriptorTest.java @@ -0,0 +1,36 @@ +/* + * 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.impl; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.databind.JsonNode; +import io.serverlessworkflow.impl.expressions.DateTimeDescriptor; +import io.serverlessworkflow.impl.jackson.JsonUtils; +import java.time.Instant; +import org.junit.jupiter.api.Test; + +class DateTimeDescriptorTest { + + @Test + void serializeDate() { + DateTimeDescriptor descriptor = DateTimeDescriptor.from(Instant.now()); + + JsonNode node = JsonUtils.fromValue(descriptor); + assertThat(node.get("iso8601").isTextual()).isTrue(); + assertThat(node.get("epoch").isObject()).isTrue(); + } +} diff --git a/impl/jackson/src/test/java/io/serverlessworkflow/impl/EventDefinitionTest.java b/impl/jackson/src/test/java/io/serverlessworkflow/impl/EventDefinitionTest.java new file mode 100644 index 00000000..353b777b --- /dev/null +++ b/impl/jackson/src/test/java/io/serverlessworkflow/impl/EventDefinitionTest.java @@ -0,0 +1,135 @@ +/* + * 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.impl; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.serverlessworkflow.api.WorkflowReader; +import io.serverlessworkflow.impl.jackson.JsonUtils; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class EventDefinitionTest { + + private static WorkflowApplication appl; + + @BeforeAll + static void init() { + appl = WorkflowApplication.builder().build(); + } + + @ParameterizedTest + @MethodSource("eventListenerParameters") + void testEventListened(String listen, String emit, JsonNode expectedResult, Object emitInput) + throws IOException { + WorkflowDefinition listenDefinition = + appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath(listen)); + WorkflowDefinition emitDefinition = + appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath(emit)); + WorkflowInstance waitingInstance = listenDefinition.instance(Map.of()); + CompletableFuture future = waitingInstance.start(); + assertThat(waitingInstance.status()).isEqualTo(WorkflowStatus.WAITING); + emitDefinition.instance(emitInput).start().join(); + assertThat(future).isCompleted(); + assertThat(waitingInstance.status()).isEqualTo(WorkflowStatus.COMPLETED); + assertThat(waitingInstance.outputAs(JsonNode.class)).isEqualTo(expectedResult); + } + + @ParameterizedTest + @MethodSource("eventsListenerParameters") + void testEventsListened(String listen, String emit1, String emit2, JsonNode expectedResult) + throws IOException { + WorkflowDefinition listenDefinition = + appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath(listen)); + WorkflowDefinition emitDoctorDefinition = + appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath(emit1)); + WorkflowDefinition emitOutDefinition = + appl.workflowDefinition(WorkflowReader.readWorkflowFromClasspath(emit2)); + WorkflowInstance waitingInstance = listenDefinition.instance(Map.of()); + CompletableFuture future = waitingInstance.start(); + assertThat(waitingInstance.status()).isEqualTo(WorkflowStatus.WAITING); + emitDoctorDefinition.instance(Map.of("temperature", 35)).start().join(); + assertThat(waitingInstance.status()).isEqualTo(WorkflowStatus.WAITING); + emitDoctorDefinition.instance(Map.of("temperature", 39)).start().join(); + assertThat(waitingInstance.status()).isEqualTo(WorkflowStatus.WAITING); + emitOutDefinition.instance(Map.of()).start().join(); + assertThat(future).isCompleted(); + assertThat(waitingInstance.status()).isEqualTo(WorkflowStatus.COMPLETED); + assertThat(waitingInstance.outputAs(JsonNode.class)).isEqualTo(expectedResult); + } + + private static Stream eventListenerParameters() { + return Stream.of( + Arguments.of("listen-to-any.yaml", "emit.yaml", array(cruellaDeVil()), Map.of()), + Arguments.of( + "listen-to-any-filter.yaml", "emit-doctor.yaml", doctor(), Map.of("temperature", 39))); + } + + private static Stream eventsListenerParameters() { + return Stream.of( + Arguments.of( + "listen-to-all.yaml", + "emit-doctor.yaml", + "emit.yaml", + array(temperature(), cruellaDeVil())), + Arguments.of( + "listen-to-any-until-consumed.yaml", + "emit-doctor.yaml", + "emit-out.yaml", + array(temperature()))); + } + + private static JsonNode cruellaDeVil() { + ObjectMapper mapper = JsonUtils.mapper(); + ObjectNode node = mapper.createObjectNode(); + node.set( + "client", mapper.createObjectNode().put("firstName", "Cruella").put("lastName", "de Vil")); + node.set( + "items", + mapper + .createArrayNode() + .add(mapper.createObjectNode().put("breed", "dalmatian").put("quantity", 101))); + return node; + } + + private static JsonNode doctor() { + ObjectNode node = temperature(); + node.put("isSick", true); + return array(node); + } + + private static ObjectNode temperature() { + ObjectNode node = JsonUtils.mapper().createObjectNode(); + node.put("temperature", 39); + return node; + } + + private static JsonNode array(JsonNode... jsonNodes) { + ArrayNode arrayNode = JsonUtils.mapper().createArrayNode(); + for (JsonNode node : jsonNodes) arrayNode.add(node); + return arrayNode; + } +} diff --git a/impl/jackson/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java b/impl/jackson/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java new file mode 100644 index 00000000..1a338248 --- /dev/null +++ b/impl/jackson/src/test/java/io/serverlessworkflow/impl/WorkflowDefinitionTest.java @@ -0,0 +1,195 @@ +/* + * 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.impl; + +import static io.serverlessworkflow.api.WorkflowReader.readWorkflowFromClasspath; +import static io.serverlessworkflow.api.WorkflowReader.validation; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowableOfType; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import io.serverlessworkflow.impl.jackson.JsonUtils; +import java.io.IOException; +import java.time.Instant; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.CompletionException; +import java.util.function.Consumer; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class WorkflowDefinitionTest { + + private static WorkflowApplication appl; + private static Instant before; + + @BeforeAll + static void init() { + appl = WorkflowApplication.builder().build(); + before = Instant.now(); + } + + @ParameterizedTest + @MethodSource("provideParameters") + void testWorkflowExecution(String fileName, Consumer assertions) + throws IOException { + assertions.accept(appl.workflowDefinition(readWorkflowFromClasspath(fileName, validation()))); + } + + private static Stream provideParameters() { + return Stream.of( + args( + "switch-then-string.yaml", + Map.of("orderType", "electronic"), + o -> assertThat(o).isEqualTo(Map.of("validate", true, "status", "fulfilled"))), + args( + "switch-then-string.yaml", + Map.of("orderType", "physical"), + o -> + assertThat(o) + .isEqualTo(Map.of("inventory", "clear", "items", 1, "address", "Elmer St"))), + args( + "switch-then-string.yaml", + Map.of("orderType", "unknown"), + o -> assertThat(o).isEqualTo(Map.of("log", "warn", "message", "something's wrong"))), + args( + "for-sum.yaml", + Map.of("input", Arrays.asList(1, 2, 3)), + o -> assertThat(o).isEqualTo(6)), + args( + "for-collect.yaml", + Map.of("input", Arrays.asList(1, 2, 3)), + o -> assertThat(o).isEqualTo(Map.of("output", Arrays.asList(2, 4, 6)))), + args( + "simple-expression.yaml", + Map.of("input", Arrays.asList(1, 2, 3)), + WorkflowDefinitionTest::checkSpecialKeywords), + args( + "conditional-set.yaml", + Map.of("enabled", true), + WorkflowDefinitionTest::checkEnableCondition), + args( + "conditional-set.yaml", + Map.of("enabled", false), + WorkflowDefinitionTest::checkDisableCondition), + args( + "raise-inline copy.yaml", + WorkflowDefinitionTest::checkWorkflowException, + WorkflowException.class), + args( + "raise-reusable.yaml", + WorkflowDefinitionTest::checkWorkflowException, + WorkflowException.class), + args( + "fork.yaml", + Map.of(), + o -> assertThat(((Map) o).get("patientId")).isIn("John", "Smith")), + argsJson("fork-no-compete.yaml", Map.of(), WorkflowDefinitionTest::checkNotCompeteOuput)); + } + + private static Arguments args( + String fileName, Map input, Consumer instance) { + return Arguments.of( + fileName, + (Consumer) + d -> + instance.accept( + d.instance(input) + .start() + .thenApply(model -> JsonUtils.toJavaValue(JsonUtils.modelToJson(model))) + .join())); + } + + private static Arguments argsJson( + String fileName, Map input, Consumer instance) { + return Arguments.of( + fileName, + (Consumer) d -> instance.accept(d.instance(input).start().join())); + } + + private static Arguments args( + String fileName, Consumer consumer, Class clazz) { + return Arguments.of( + fileName, + (Consumer) + d -> + checkWorkflowException( + catchThrowableOfType( + CompletionException.class, () -> d.instance(Map.of()).start().join()), + consumer, + clazz)); + } + + private static void checkWorkflowException( + CompletionException ex, Consumer consumer, Class clazz) { + assertThat(ex.getCause()).isInstanceOf(clazz); + consumer.accept(clazz.cast(ex.getCause())); + } + + private static void checkNotCompeteOuput(WorkflowModel model) { + JsonNode out = + model + .as(JsonNode.class) + .orElseThrow( + () -> new IllegalArgumentException("Model cannot be converted to json node")); + assertThat(out).isInstanceOf(ArrayNode.class); + assertThat(out).hasSize(2); + ArrayNode array = (ArrayNode) out; + assertThat(array) + .containsExactlyInAnyOrder( + createObjectNode("callNurse", "patientId", "John", "room", 1), + createObjectNode("callDoctor", "patientId", "Smith", "room", 2)); + } + + private static JsonNode createObjectNode( + String parent, String key1, String value1, String key2, int value2) { + return JsonUtils.mapper() + .createObjectNode() + .set(parent, JsonUtils.mapper().createObjectNode().put(key1, value1).put(key2, value2)); + } + + private static void checkWorkflowException(WorkflowException ex) { + assertThat(ex.getWorflowError().type()) + .isEqualTo("https://serverlessworkflow.io/errors/not-implemented"); + assertThat(ex.getWorflowError().status()).isEqualTo(500); + assertThat(ex.getWorflowError().title()).isEqualTo("Not Implemented"); + assertThat(ex.getWorflowError().details()).contains("raise-not-implemented"); + assertThat(ex.getWorflowError().instance()).isEqualTo("do/0/notImplemented"); + } + + private static void checkSpecialKeywords(Object obj) { + Map result = (Map) obj; + assertThat(Instant.ofEpochMilli((long) result.get("startedAt"))) + .isAfterOrEqualTo(before) + .isBeforeOrEqualTo(Instant.now()); + assertThat(result.get("id").toString()).hasSize(26); + assertThat(result.get("version").toString()).contains("alpha"); + } + + private static void checkEnableCondition(Object obj) { + Map result = (Map) obj; + assertThat(result.get("name")).isEqualTo("javierito"); + } + + private static void checkDisableCondition(Object obj) { + Map result = (Map) obj; + assertThat(result.get("enabled")).isEqualTo(false); + } +} diff --git a/impl/jackson/src/test/resources/conditional-set.yaml b/impl/jackson/src/test/resources/conditional-set.yaml new file mode 100644 index 00000000..5e06622d --- /dev/null +++ b/impl/jackson/src/test/resources/conditional-set.yaml @@ -0,0 +1,10 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: conditional-set + version: '0.1.0' +do: + - conditionalExpression: + if: .enabled + set: + name: javierito \ No newline at end of file diff --git a/impl/jackson/src/test/resources/emit-doctor.yaml b/impl/jackson/src/test/resources/emit-doctor.yaml new file mode 100644 index 00000000..b940b9cd --- /dev/null +++ b/impl/jackson/src/test/resources/emit-doctor.yaml @@ -0,0 +1,14 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: emit-doctor + version: '0.1.0' +do: + - emitEvent: + emit: + event: + with: + source: https://hospital.com + type: com.fake-hospital.vitals.measurements.temperature + data: + temperature: ${.temperature} \ No newline at end of file diff --git a/impl/jackson/src/test/resources/emit-out.yaml b/impl/jackson/src/test/resources/emit-out.yaml new file mode 100644 index 00000000..41582f34 --- /dev/null +++ b/impl/jackson/src/test/resources/emit-out.yaml @@ -0,0 +1,12 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: emit-out + version: '0.1.0' +do: + - emitEvent: + emit: + event: + with: + source: https://hospital.com + type: com.fake-hospital.patient.checked-out \ No newline at end of file diff --git a/impl/jackson/src/test/resources/emit.yaml b/impl/jackson/src/test/resources/emit.yaml new file mode 100644 index 00000000..d4d6d559 --- /dev/null +++ b/impl/jackson/src/test/resources/emit.yaml @@ -0,0 +1,19 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: emit + version: '0.1.0' +do: + - emitEvent: + emit: + event: + with: + source: https://petstore.com + type: com.petstore.order.placed.v1 + data: + client: + firstName: Cruella + lastName: de Vil + items: + - breed: dalmatian + quantity: 101 \ No newline at end of file diff --git a/impl/jackson/src/test/resources/for-collect.yaml b/impl/jackson/src/test/resources/for-collect.yaml new file mode 100644 index 00000000..53dd8231 --- /dev/null +++ b/impl/jackson/src/test/resources/for-collect.yaml @@ -0,0 +1,17 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: for-collect-example + version: '0.1.0' +do: + - sumAll: + for: + each: number + in: .input + at: index + input: + from: '{input: .input, output: []}' + do: + - sumIndex: + set: + output: ${.output+[$number+$index+1]} \ No newline at end of file diff --git a/impl/jackson/src/test/resources/for-sum.yaml b/impl/jackson/src/test/resources/for-sum.yaml new file mode 100644 index 00000000..6d89d9ff --- /dev/null +++ b/impl/jackson/src/test/resources/for-sum.yaml @@ -0,0 +1,16 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: for-sum-example + version: '0.1.0' +do: + - sumAll: + for: + each: number + in: .input + do: + - accumulate: + set: + counter: ${.counter+$number} + output: + as: .counter diff --git a/impl/jackson/src/test/resources/fork-no-compete.yaml b/impl/jackson/src/test/resources/fork-no-compete.yaml new file mode 100644 index 00000000..ee882f13 --- /dev/null +++ b/impl/jackson/src/test/resources/fork-no-compete.yaml @@ -0,0 +1,28 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: fork-example + version: '0.1.0' +do: + - callSomeone: + fork: + compete: false + branches: + - callNurse: + do: + - waitForNurse: + wait: + milliseconds: 500 + - nurseArrived: + set: + patientId: John + room: 1 + - callDoctor: + do: + - waitForDoctor: + wait: + milliseconds: 499 + - doctorArrived: + set: + patientId: Smith + room: 2 \ No newline at end of file diff --git a/impl/jackson/src/test/resources/fork.yaml b/impl/jackson/src/test/resources/fork.yaml new file mode 100644 index 00000000..e9f3e7a3 --- /dev/null +++ b/impl/jackson/src/test/resources/fork.yaml @@ -0,0 +1,18 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: fork-no-compete + version: '0.1.0' +do: + - callSomeone: + fork: + compete: true + branches: + - callNurse: + set: + patientId: John + room: 1 + - callDoctor: + set: + patientId: Smith + room: 2 \ No newline at end of file diff --git a/impl/jackson/src/test/resources/listen-to-all.yaml b/impl/jackson/src/test/resources/listen-to-all.yaml new file mode 100644 index 00000000..0d55f185 --- /dev/null +++ b/impl/jackson/src/test/resources/listen-to-all.yaml @@ -0,0 +1,15 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: listen-to-all + version: '0.1.0' +do: + - callDoctor: + listen: + to: + all: + - with: + type: com.fake-hospital.vitals.measurements.temperature + data: ${ .temperature > 38 } + - with: + type: com.petstore.order.placed.v1 \ No newline at end of file diff --git a/impl/jackson/src/test/resources/listen-to-any-filter.yaml b/impl/jackson/src/test/resources/listen-to-any-filter.yaml new file mode 100644 index 00000000..49185870 --- /dev/null +++ b/impl/jackson/src/test/resources/listen-to-any-filter.yaml @@ -0,0 +1,25 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: listen-to-any-filter + version: '0.1.0' +do: + - callDoctor: + listen: + to: + any: + - with: + type: com.fake-hospital.vitals.measurements.temperature + data: ${ .temperature > 38 } + - with: + type: com.fake-hospital.vitals.measurements.bpm + data: ${ .bpm < 60 or .bpm > 100 } + until: ( . | length ) > 0 + foreach: + item: event + do: + - isSick: + set: + temperature: ${$event.temperature} + isSick: true + \ No newline at end of file diff --git a/impl/jackson/src/test/resources/listen-to-any-until-consumed.yaml b/impl/jackson/src/test/resources/listen-to-any-until-consumed.yaml new file mode 100644 index 00000000..62f04d2d --- /dev/null +++ b/impl/jackson/src/test/resources/listen-to-any-until-consumed.yaml @@ -0,0 +1,20 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: listen-to-any-until-consumed + version: '0.1.0' +do: + - callDoctor: + listen: + to: + any: + - with: + type: com.fake-hospital.vitals.measurements.temperature + data: ${ .temperature > 38 } + - with: + type: com.fake-hospital.vitals.measurements.bpm + data: ${ .bpm < 60 or .bpm > 100 } + until: + one: + with: + type: com.fake-hospital.patient.checked-out \ No newline at end of file diff --git a/impl/jackson/src/test/resources/listen-to-any.yaml b/impl/jackson/src/test/resources/listen-to-any.yaml new file mode 100644 index 00000000..b4a9fcb9 --- /dev/null +++ b/impl/jackson/src/test/resources/listen-to-any.yaml @@ -0,0 +1,10 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: listen-to-any + version: '0.1.0' +do: + - callDoctor: + listen: + to: + any: [] \ No newline at end of file diff --git a/impl/jackson/src/test/resources/raise-inline copy.yaml b/impl/jackson/src/test/resources/raise-inline copy.yaml new file mode 100644 index 00000000..b4bcac88 --- /dev/null +++ b/impl/jackson/src/test/resources/raise-inline copy.yaml @@ -0,0 +1,13 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: raise-not-implemented + version: '0.1.0' +do: + - notImplemented: + raise: + error: + type: https://serverlessworkflow.io/errors/not-implemented + status: 500 + title: Not Implemented + detail: ${ "The workflow '\( $workflow.definition.document.name ):\( $workflow.definition.document.version )' is a work in progress and cannot be run yet" } \ No newline at end of file diff --git a/impl/jackson/src/test/resources/raise-reusable.yaml b/impl/jackson/src/test/resources/raise-reusable.yaml new file mode 100644 index 00000000..6955bd05 --- /dev/null +++ b/impl/jackson/src/test/resources/raise-reusable.yaml @@ -0,0 +1,16 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: raise-not-implemented-reusable + version: '0.1.0' +use: + errors: + notImplemented: + type: https://serverlessworkflow.io/errors/not-implemented + status: 500 + title: Not Implemented + detail: ${ "The workflow '\( $workflow.definition.document.name ):\( $workflow.definition.document.version )' is a work in progress and cannot be run yet" } +do: + - notImplemented: + raise: + error: notImplemented \ No newline at end of file diff --git a/impl/jackson/src/test/resources/simple-expression.yaml b/impl/jackson/src/test/resources/simple-expression.yaml new file mode 100644 index 00000000..4e240d6b --- /dev/null +++ b/impl/jackson/src/test/resources/simple-expression.yaml @@ -0,0 +1,11 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: simple-expression + version: '0.1.0' +do: + - useExpression: + set: + startedAt: ${$task.startedAt.epoch.milliseconds} + id : ${$workflow.id} + version: ${$runtime.version} \ No newline at end of file diff --git a/impl/jackson/src/test/resources/switch-then-string.yaml b/impl/jackson/src/test/resources/switch-then-string.yaml new file mode 100644 index 00000000..4093a6fa --- /dev/null +++ b/impl/jackson/src/test/resources/switch-then-string.yaml @@ -0,0 +1,31 @@ +document: + dsl: 1.0.0-alpha5 + namespace: test + name: switch + version: 0.1.0 +do: + - processOrder: + switch: + - case1: + when: .orderType == "electronic" + then: processElectronicOrder + - case2: + when: .orderType == "physical" + then: processPhysicalOrder + - default: + then: handleUnknownOrderType + - processElectronicOrder: + set: + validate: true + status: fulfilled + then: exit + - processPhysicalOrder: + set: + inventory: clear + items: 1 + address: Elmer St + then: exit + - handleUnknownOrderType: + set: + log: warn + message: something's wrong diff --git a/impl/pom.xml b/impl/pom.xml new file mode 100644 index 00000000..4ecd7a7b --- /dev/null +++ b/impl/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-parent + 8.0.0-SNAPSHOT + + serverlessworkflow-impl + Serverless Workflow :: Impl + pom + + 3.1.10 + 4.0.1 + 1.3.0 + 5.2.3 + 3.1.0 + + + + + io.serverlessworkflow + serverlessworkflow-impl-core + ${project.version} + + + io.serverlessworkflow + serverlessworkflow-impl-http + ${project.version} + + + io.serverlessworkflow + serverlessworkflow-impl-jackson + ${project.version} + + + io.cloudevents + cloudevents-core + ${version.io.cloudevents} + + + io.cloudevents + cloudevents-json-jackson + ${version.io.cloudevents} + + + net.thisptr + jackson-jq + ${version.net.thisptr} + + + com.github.f4b6a3 + ulid-creator + ${version.com.github.f4b6a3} + + + jakarta.ws.rs + jakarta.ws.rs-api + ${version.jakarta.ws.rs} + + + org.glassfish.jersey.core + jersey-client + ${version.org.glassfish.jersey} + test + + + org.glassfish.jersey.media + jersey-media-json-jackson + ${version.org.glassfish.jersey} + test + + + + + http + core + jackson + + \ No newline at end of file diff --git a/maintainer_guidelines.md b/maintainer_guidelines.md index ecd1b11a..d40d33bf 100644 --- a/maintainer_guidelines.md +++ b/maintainer_guidelines.md @@ -16,7 +16,7 @@ Here are a few tips for repository maintainers. ## Branch Management -The `main` branch is is the bleeding edge. New major versions of the module +The `main` branch is the bleeding edge. New major versions of the module are cut from this branch and tagged. If you intend to submit a pull request you should use `main HEAD` as your starting point. diff --git a/pom.xml b/pom.xml index 394d6910..471a0028 100644 --- a/pom.xml +++ b/pom.xml @@ -1,17 +1,23 @@ - + 4.0.0 io.serverlessworkflow serverlessworkflow-parent - 3.0.0-SNAPSHOT + 8.0.0-SNAPSHOT pom 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// @@ -19,62 +25,105 @@ The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt + https://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 - + + api + impl + types + annotations + generators + serialization + examples + experimental + fluent + - 1.8 - 1.8 - 1.8 + 17 + ${java.version} + ${java.version} UTF-8 - 3.6.2 - 3.0.0-M2 - 8 - 3.8.1 - 2.22.0 - 2.22.0 - 2.8.2 + 3.9.11 - 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 + + 3.2.1 + 3.6.0 + 3.14.0 + 3.1.4 + 3.6.1 + 3.5.3 + 2.27 + 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 + + + + 1.5.18 + 2.19.2 + 1.5.8 + 3.1.1 + 1.5.2 + 3.27.3 + 5.13.4 + 5.18.0 + 2.0.17 + 9.0.1.Final + 6.0.0 + 1.3.0-beta9-SNAPSHOT + + true + + + java + true + - org.slf4j - slf4j-api - ${version.org.slf4j} - - - org.slf4j - jcl-over-slf4j - ${version.org.slf4j} + com.fasterxml.jackson + jackson-bom + ${version.com.fasterxml.jackson} + pom + import com.fasterxml.jackson.core @@ -92,45 +141,49 @@ ${version.com.fasterxml.jackson} - javax.validation - validation-api - ${version.javax.validation} + com.fasterxml.jackson.core + jackson-annotations + ${version.com.fasterxml.jackson} + + + org.slf4j + slf4j-api + ${version.org.slf4j} + + + org.slf4j + slf4j-simple + ${version.org.slf4j} - org.apache.commons - commons-lang3 - ${commons.lang.version} + io.serverlessworkflow + serverlessworkflow-api + ${project.version} - com.github.erosb - everit-json-schema - ${json.schema.validation.version} - - - commons-logging - commons-logging - - + com.networknt + json-schema-validator + ${version.com.networknt} - org.json - json - ${version.json} + org.hibernate.validator + hibernate-validator + ${version.org.hibernate.validator} - org.thymeleaf - thymeleaf - ${version.thymeleaf} + org.glassfish.expressly + expressly + ${version.org.glassfish.expressly} - net.sourceforge.plantuml - plantuml - ${version.plantuml} + org.jsonschema2pojo + jsonschema2pojo-core + ${version.jsonschema2pojo-maven-plugin} - guru.nidi - graphviz-java - ${version.graphviz} + jakarta.validation + jakarta.validation-api + ${version.jakarta.validation} @@ -171,23 +224,133 @@ test - org.hamcrest - hamcrest-library - ${hamcrest.version} - test - - - org.skyscreamer - jsonassert - ${jsonassert.version} - test + dev.langchain4j + langchain4j-agentic + ${version.dev.langchain4j} + + + + 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.apache.maven.plugins + maven-checkstyle-plugin + + + + + + + + + + + + + + + + ${project.build.directory}/checkstyle.log + true + true + true + false + false + ${checkstyle.logViolationsToConsole} + ${checkstyle.failOnViolation} + + ${project.build.sourceDirectory} + ${project.build.testSourceDirectory} + + + + + compile + + check + + + + + + com.spotify.fmt + fmt-maven-plugin + + src/main/java + src/test/java + false + .*\.java + false + false + + + + + + format + + + + + + + + + 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} @@ -219,14 +382,53 @@ - maven-compiler-plugin - ${version.compiler.plugin} + 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} - true - true - - -Xlint:unchecked - + clean install + true + @{project.version} + false + true + false @@ -239,7 +441,7 @@ maven-surefire-plugin ${version.surefire.plugin} - -Xmx1024m -XX:MaxPermSize=256m + -Xmx1024m -XX:+IgnoreUnrecognizedVMOptions -XX:MaxPermSize=256m @@ -247,7 +449,54 @@ maven-failsafe-plugin ${version.failsafe.plugin} - -Xmx1024m -XX:MaxPermSize=256m + -Xmx1024m -XX:+IgnoreUnrecognizedVMOptions -XX:MaxPermSize=256m + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${version.checkstyle.plugin} + + + com.spotify.fmt + fmt-maven-plugin + ${version.fmt-maven-plugin} + + + org.apache.maven.plugins + maven-jar-plugin + ${version.jar.plugin} + + + true + + + true + + + true + + + + ${project.url} + ${java.version} + ${java.vendor} + ${os.name} + ${os.arch} + ${os.version} + ${project.scm.url} + ${project.scm.connection} + ${buildNumber} + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${version.javadoc.plugin} + + false @@ -256,7 +505,7 @@ - ossrh + ossrh-snapshots https://oss.sonatype.org/content/repositories/snapshots @@ -276,4 +525,45 @@ + + + + release + + + + org.apache.maven.plugins + maven-gpg-plugin + + + --pinentry-mode + loopback + + + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + package + + jar + + + + + + + + diff --git a/serialization/pom.xml b/serialization/pom.xml new file mode 100644 index 00000000..6e8411f0 --- /dev/null +++ b/serialization/pom.xml @@ -0,0 +1,25 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-parent + 8.0.0-SNAPSHOT + + serverlessworkflow-serialization + Serverless Workflow :: Serialization + + + io.serverlessworkflow + serverlessworkflow-annotations + ${project.version} + + + com.fasterxml.jackson.core + jackson-databind + + + jakarta.validation + jakarta.validation-api + + + \ No newline at end of file diff --git a/serialization/src/main/java/io/serverlessworkflow/serialization/DeserializeHelper.java b/serialization/src/main/java/io/serverlessworkflow/serialization/DeserializeHelper.java new file mode 100644 index 00000000..041474d9 --- /dev/null +++ b/serialization/src/main/java/io/serverlessworkflow/serialization/DeserializeHelper.java @@ -0,0 +1,90 @@ +/* + * 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.serialization; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.databind.JsonMappingException; +import io.serverlessworkflow.annotations.OneOfSetter; +import jakarta.validation.ConstraintViolationException; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; + +public class DeserializeHelper { + + public static T deserializeOneOf( + JsonParser p, Class targetClass, Collection> oneOfTypes) throws IOException { + TreeNode node = p.readValueAsTree(); + try { + T result = targetClass.getDeclaredConstructor().newInstance(); + Collection exceptions = new ArrayList<>(); + for (Class oneOfType : oneOfTypes) { + try { + assingIt(p, result, node, targetClass, oneOfType); + break; + } catch (IOException | ConstraintViolationException | InvocationTargetException ex) { + exceptions.add(ex); + } + } + if (exceptions.size() == oneOfTypes.size()) { + JsonMappingException ex = + new JsonMappingException( + p, + String.format( + "Error deserializing class %s, all oneOf alternatives %s has failed ", + targetClass, oneOfTypes)); + exceptions.forEach(ex::addSuppressed); + throw ex; + } + return result; + } catch (ReflectiveOperationException ex) { + throw new IllegalStateException(ex); + } + } + + private static void assingIt( + JsonParser p, T result, TreeNode node, Class targetClass, Class type) + throws JsonProcessingException, ReflectiveOperationException { + findSetMethod(targetClass, type).invoke(result, p.getCodec().treeToValue(node, type)); + } + + private static Method findSetMethod(Class targetClass, Class type) { + for (Method method : targetClass.getMethods()) { + OneOfSetter oneOfSetter = method.getAnnotation(OneOfSetter.class); + if (oneOfSetter != null && type.equals(oneOfSetter.value())) { + return method; + } + } + throw new IllegalStateException("Cannot find a setter for type " + type); + } + + public static T deserializeItem(JsonParser p, Class targetClass, Class valueClass) + throws IOException { + TreeNode node = p.readValueAsTree(); + String fieldName = node.fieldNames().next(); + try { + return targetClass + .getConstructor(String.class, valueClass) + .newInstance(fieldName, p.getCodec().treeToValue(node.get(fieldName), valueClass)); + } catch (ReflectiveOperationException e) { + throw new IOException(e); + } + } +} diff --git a/serialization/src/main/java/io/serverlessworkflow/serialization/SerializeHelper.java b/serialization/src/main/java/io/serverlessworkflow/serialization/SerializeHelper.java new file mode 100644 index 00000000..47ab1a5e --- /dev/null +++ b/serialization/src/main/java/io/serverlessworkflow/serialization/SerializeHelper.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.serialization; + +import com.fasterxml.jackson.core.JsonGenerator; +import io.serverlessworkflow.annotations.OneOfValueProvider; +import java.io.IOException; + +public class SerializeHelper { + public static void serializeOneOf(JsonGenerator jgen, OneOfValueProvider item) + throws IOException { + jgen.writeObject(item.get()); + } +} diff --git a/spi/.gitignore b/spi/.gitignore deleted file mode 100644 index d4dfde66..00000000 --- a/spi/.gitignore +++ /dev/null @@ -1,31 +0,0 @@ -HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/** -!**/src/test/** - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -build/ - -### VS Code ### -.vscode/ \ No newline at end of file diff --git a/spi/src/main/java/io/serverlessworkflow/spi/WorkflowDiagramProvider.java b/spi/src/main/java/io/serverlessworkflow/spi/WorkflowDiagramProvider.java deleted file mode 100644 index ce1457b6..00000000 --- a/spi/src/main/java/io/serverlessworkflow/spi/WorkflowDiagramProvider.java +++ /dev/null @@ -1,52 +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.spi; - -import io.serverlessworkflow.api.interfaces.WorkflowDiagram; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Iterator; -import java.util.ServiceLoader; - -public class WorkflowDiagramProvider { - private WorkflowDiagram workflowDiagram; - - private static Logger logger = LoggerFactory.getLogger(WorkflowDiagramProvider.class); - - public WorkflowDiagramProvider() { - ServiceLoader foundWorkflowDiagrams = ServiceLoader.load(WorkflowDiagram.class); - Iterator it = foundWorkflowDiagrams.iterator(); - if (it.hasNext()) { - workflowDiagram = it.next(); - logger.info("Found workflow diagram: " + workflowDiagram.toString()); - } - } - - private static class LazyHolder { - - static final WorkflowDiagramProvider INSTANCE = new WorkflowDiagramProvider(); - } - - public static WorkflowDiagramProvider getInstance() { - return WorkflowDiagramProvider.LazyHolder.INSTANCE; - } - - public WorkflowDiagram get() { - return workflowDiagram; - } -} diff --git a/spi/src/main/java/io/serverlessworkflow/spi/WorkflowPropertySourceProvider.java b/spi/src/main/java/io/serverlessworkflow/spi/WorkflowPropertySourceProvider.java deleted file mode 100644 index 1c63c4df..00000000 --- a/spi/src/main/java/io/serverlessworkflow/spi/WorkflowPropertySourceProvider.java +++ /dev/null @@ -1,52 +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.spi; - -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Iterator; -import java.util.ServiceLoader; - -public class WorkflowPropertySourceProvider { - private WorkflowPropertySource workflowPropertySource; - - private static Logger logger = LoggerFactory.getLogger(WorkflowValidatorProvider.class); - - public WorkflowPropertySourceProvider() { - ServiceLoader foundPropertyContext = ServiceLoader.load(WorkflowPropertySource.class); - Iterator it = foundPropertyContext.iterator(); - if (it.hasNext()) { - workflowPropertySource = it.next(); - logger.info("Found property source: " + workflowPropertySource.toString()); - } - } - - private static class LazyHolder { - - static final WorkflowPropertySourceProvider INSTANCE = new WorkflowPropertySourceProvider(); - } - - public static WorkflowPropertySourceProvider getInstance() { - return WorkflowPropertySourceProvider.LazyHolder.INSTANCE; - } - - public WorkflowPropertySource get() { - return workflowPropertySource; - } -} diff --git a/spi/src/main/java/io/serverlessworkflow/spi/WorkflowValidatorProvider.java b/spi/src/main/java/io/serverlessworkflow/spi/WorkflowValidatorProvider.java deleted file mode 100644 index c033c63d..00000000 --- a/spi/src/main/java/io/serverlessworkflow/spi/WorkflowValidatorProvider.java +++ /dev/null @@ -1,52 +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.spi; - -import io.serverlessworkflow.api.interfaces.WorkflowValidator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Iterator; -import java.util.ServiceLoader; - -public class WorkflowValidatorProvider { - private WorkflowValidator workflowValidator; - - private static Logger logger = LoggerFactory.getLogger(WorkflowValidatorProvider.class); - - public WorkflowValidatorProvider() { - ServiceLoader foundWorkflowValidators = ServiceLoader.load(WorkflowValidator.class); - Iterator it = foundWorkflowValidators.iterator(); - if (it.hasNext()) { - workflowValidator = it.next(); - logger.info("Found workflow validator: " + workflowValidator.toString()); - } - } - - private static class LazyHolder { - - static final WorkflowValidatorProvider INSTANCE = new WorkflowValidatorProvider(); - } - - public static WorkflowValidatorProvider getInstance() { - return LazyHolder.INSTANCE; - } - - public WorkflowValidator get() { - return workflowValidator; - } -} diff --git a/spi/src/test/java/io/serverlessworkflow/spi/test/ServiceProvidersTest.java b/spi/src/test/java/io/serverlessworkflow/spi/test/ServiceProvidersTest.java deleted file mode 100644 index 3fd356b2..00000000 --- a/spi/src/test/java/io/serverlessworkflow/spi/test/ServiceProvidersTest.java +++ /dev/null @@ -1,45 +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.spi.test; - -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; -import io.serverlessworkflow.api.interfaces.WorkflowValidator; -import io.serverlessworkflow.spi.WorkflowPropertySourceProvider; -import io.serverlessworkflow.spi.WorkflowValidatorProvider; -import io.serverlessworkflow.spi.test.providers.TestWorkflowPropertySource; -import io.serverlessworkflow.spi.test.providers.TestWorkflowValidator; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.util.Map; - -public class ServiceProvidersTest { - - @Test - public void testWorkflowValidatorProvider() { - WorkflowValidator validator = WorkflowValidatorProvider.getInstance().get(); - Assertions.assertNotNull(validator); - Assertions.assertTrue(validator instanceof TestWorkflowValidator); - } - - @Test - public void testWorkflowPropertySourceProvider() { - WorkflowPropertySource propertySource = WorkflowPropertySourceProvider.getInstance().get(); - Assertions.assertNotNull(propertySource); - Assertions.assertTrue(propertySource instanceof TestWorkflowPropertySource); - } -} \ No newline at end of file diff --git a/spi/src/test/java/io/serverlessworkflow/spi/test/providers/TestWorkflowPropertySource.java b/spi/src/test/java/io/serverlessworkflow/spi/test/providers/TestWorkflowPropertySource.java deleted file mode 100644 index 07fc5a66..00000000 --- a/spi/src/test/java/io/serverlessworkflow/spi/test/providers/TestWorkflowPropertySource.java +++ /dev/null @@ -1,50 +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.spi.test.providers; - -import io.serverlessworkflow.api.interfaces.WorkflowPropertySource; - -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - -public class TestWorkflowPropertySource implements WorkflowPropertySource { - - private Properties source = new Properties(); - - @Override - public Properties getPropertySource() { - Map propertySourcetMap = new HashMap<>(); - propertySourcetMap.put("wfname", - "test-wf"); - propertySourcetMap.put("delaystate.name", - "delay-state"); - propertySourcetMap.put("delaystate.timedelay", - "PT5S"); - propertySourcetMap.put("delaystate.type", - "DELAY"); - - source.putAll(propertySourcetMap); - - return source; - } - - @Override - public void setPropertySource(Properties source) { - this.source = source; - } -} diff --git a/spi/src/test/java/io/serverlessworkflow/spi/test/providers/TestWorkflowValidator.java b/spi/src/test/java/io/serverlessworkflow/spi/test/providers/TestWorkflowValidator.java deleted file mode 100644 index 3391d1e1..00000000 --- a/spi/src/test/java/io/serverlessworkflow/spi/test/providers/TestWorkflowValidator.java +++ /dev/null @@ -1,56 +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.spi.test.providers; - -import io.serverlessworkflow.api.Workflow; -import io.serverlessworkflow.api.interfaces.WorkflowValidator; -import io.serverlessworkflow.api.validation.ValidationError; - -import java.util.List; - -public class TestWorkflowValidator implements WorkflowValidator { - - @Override - public WorkflowValidator setWorkflow(Workflow workflow) { - return this; - } - - @Override - public WorkflowValidator setSource(String source) { - return this; - } - - @Override - public List validate() { - return null; - } - - @Override - public boolean isValid() { - return false; - } - - @Override - public WorkflowValidator setSchemaValidationEnabled(boolean schemaValidationEnabled) { - return this; - } - - @Override - public WorkflowValidator reset() { - return this; - } -} diff --git a/spi/src/test/resources/META-INF/services/io.serverlessworkflow.api.interfaces.WorkflowPropertySource b/spi/src/test/resources/META-INF/services/io.serverlessworkflow.api.interfaces.WorkflowPropertySource deleted file mode 100644 index ce3c644b..00000000 --- a/spi/src/test/resources/META-INF/services/io.serverlessworkflow.api.interfaces.WorkflowPropertySource +++ /dev/null @@ -1 +0,0 @@ -io.serverlessworkflow.spi.test.providers.TestWorkflowPropertySource \ No newline at end of file diff --git a/spi/src/test/resources/META-INF/services/io.serverlessworkflow.api.interfaces.WorkflowValidator b/spi/src/test/resources/META-INF/services/io.serverlessworkflow.api.interfaces.WorkflowValidator deleted file mode 100644 index d25b29d9..00000000 --- a/spi/src/test/resources/META-INF/services/io.serverlessworkflow.api.interfaces.WorkflowValidator +++ /dev/null @@ -1 +0,0 @@ -io.serverlessworkflow.spi.test.providers.TestWorkflowValidator \ No newline at end of file diff --git a/types/pom.xml b/types/pom.xml new file mode 100644 index 00000000..20b4d552 --- /dev/null +++ b/types/pom.xml @@ -0,0 +1,70 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-parent + 8.0.0-SNAPSHOT + + Serverless Workflow :: Types + serverlessworkflow-types + + + io.serverlessworkflow + serverlessworkflow-annotations + ${project.version} + + + jakarta.validation + jakarta.validation-api + + + + + + org.jsonschema2pojo + jsonschema2pojo-maven-plugin + + ${basedir}/src/main/resources/schema + + + yamlschema + io.serverlessworkflow.api.types + ${project.build.directory}/generated-sources/src/main/java + true + true + true + true + false + false + true + true + true + true + ${java.version} + true + none + true + io.serverlessworkflow.generator.UnreferencedFactory + io.serverlessworkflow.generator.CustomAnnotator + + + + io.serverlessworkflow + serverless-workflow-types-generator + ${project.version} + + + + + + generate + + generate-sources + + + + + + \ No newline at end of file diff --git a/types/src/main/resources/schema/workflow.yaml b/types/src/main/resources/schema/workflow.yaml new file mode 100644 index 00000000..73bda7ee --- /dev/null +++ b/types/src/main/resources/schema/workflow.yaml @@ -0,0 +1,1792 @@ +$id: https://serverlessworkflow.io/schemas/1.0.1/workflow.yaml +$schema: https://json-schema.org/draft/2020-12/schema +description: Serverless Workflow DSL - Workflow Schema. +type: object +required: [ document, do ] +properties: + document: + type: object + title: Document + description: Documents the workflow. + unevaluatedProperties: false + properties: + dsl: + type: string + pattern: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ + title: WorkflowDSL + description: The version of the DSL used by the workflow. + namespace: + type: string + pattern: ^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$ + title: WorkflowNamespace + description: The workflow's namespace. + name: + type: string + pattern: ^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$ + title: WorkflowName + description: The workflow's name. + version: + type: string + pattern: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$ + title: WorkflowVersion + description: The workflow's semantic version. + title: + type: string + title: WorkflowTitle + description: The workflow's title. + summary: + type: string + title: WorkflowSummary + description: The workflow's Markdown summary. + tags: + type: object + title: WorkflowTags + description: A key/value mapping of the workflow's tags, if any. + additionalProperties: true + metadata: + type: object + title: WorkflowMetadata + description: Holds additional information about the workflow. + additionalProperties: true + required: [ dsl, namespace, name, version ] + input: + $ref: '#/$defs/input' + title: Input + description: Configures the workflow's input. + use: + type: object + title: Use + description: Defines the workflow's reusable components. + unevaluatedProperties: false + properties: + authentications: + type: object + title: UseAuthentications + description: The workflow's reusable authentication policies. + additionalProperties: + $ref: '#/$defs/authenticationPolicy' + errors: + type: object + title: UseErrors + description: The workflow's reusable errors. + additionalProperties: + $ref: '#/$defs/error' + extensions: + type: array + title: UseExtensions + description: The workflow's extensions. + items: + type: object + title: ExtensionItem + minProperties: 1 + maxProperties: 1 + additionalProperties: + $ref: '#/$defs/extension' + functions: + type: object + title: UseFunctions + description: The workflow's reusable functions. + additionalProperties: + $ref: '#/$defs/task' + retries: + type: object + title: UseRetries + description: The workflow's reusable retry policies. + additionalProperties: + $ref: '#/$defs/retryPolicy' + secrets: + type: array + title: UseSecrets + description: The workflow's reusable secrets. + items: + type: string + description: The workflow's secrets. + timeouts: + type: object + title: UseTimeouts + description: The workflow's reusable timeouts. + additionalProperties: + $ref: '#/$defs/timeout' + catalogs: + type: object + title: UseCatalogs + description: The workflow's reusable catalogs. + additionalProperties: + $ref: '#/$defs/catalog' + do: + $ref: '#/$defs/taskList' + title: Do + description: Defines the task(s) the workflow must perform. + timeout: + title: DoTimeout + oneOf: + - $ref: '#/$defs/timeout' + title: TimeoutDefinition + description: The workflow's timeout configuration, if any. + - type: string + title: TimeoutReference + description: The name of the workflow's timeout, if any. + output: + $ref: '#/$defs/output' + title: Output + description: Configures the workflow's output. + schedule: + type: object + title: Schedule + description: Schedules the workflow. + unevaluatedProperties: false + properties: + every: + $ref: '#/$defs/duration' + title: ScheduleEvery + description: Specifies the duration of the interval at which the workflow should be executed. + cron: + type: string + title: ScheduleCron + description: Specifies the schedule using a cron expression, e.g., '0 0 * * *' for daily at midnight. + after: + $ref: '#/$defs/duration' + title: ScheduleAfter + description: Specifies a delay duration that the workflow must wait before starting again after it completes. + on: + $ref: '#/$defs/eventConsumptionStrategy' + title: ScheduleOn + description: Specifies the events that trigger the workflow execution. +$defs: + taskList: + title: TaskList + description: List of named tasks to perform. + type: array + items: + type: object + title: TaskItem + minProperties: 1 + maxProperties: 1 + additionalProperties: + $ref: '#/$defs/task' + taskBase: + type: object + title: TaskBase + description: An object inherited by all tasks. + properties: + if: + type: string + title: TaskBaseIf + description: A runtime expression, if any, used to determine whether or not the task should be run. + input: + $ref: '#/$defs/input' + title: TaskBaseInput + description: Configure the task's input. + output: + $ref: '#/$defs/output' + title: TaskBaseOutput + description: Configure the task's output. + export: + $ref: '#/$defs/export' + title: TaskBaseExport + description: Export task output to context. + timeout: + title: TaskTimeout + oneOf: + - $ref: '#/$defs/timeout' + title: TaskTimeoutDefinition + description: The task's timeout configuration, if any. + - type: string + title: TaskTimeoutReference + description: The name of the task's timeout, if any. + then: + $ref: '#/$defs/flowDirective' + title: TaskBaseThen + description: The flow directive to be performed upon completion of the task. + metadata: + type: object + title: TaskMetadata + description: Holds additional information about the task. + additionalProperties: true + task: + title: Task + description: A discrete unit of work that contributes to achieving the overall objectives defined by the workflow. + unevaluatedProperties: false + oneOf: + - $ref: '#/$defs/callTask' + - $ref: '#/$defs/doTask' + - $ref: '#/$defs/forkTask' + - $ref: '#/$defs/emitTask' + - $ref: '#/$defs/forTask' + - $ref: '#/$defs/listenTask' + - $ref: '#/$defs/raiseTask' + - $ref: '#/$defs/runTask' + - $ref: '#/$defs/setTask' + - $ref: '#/$defs/switchTask' + - $ref: '#/$defs/tryTask' + - $ref: '#/$defs/waitTask' + callTask: + title: CallTask + description: Defines the call to perform. + oneOf: + - title: CallAsyncAPI + description: Defines the AsyncAPI call to perform. + type: object + required: [ call, with ] + unevaluatedProperties: false + allOf: + - $ref: '#/$defs/taskBase' + - properties: + call: + type: string + const: asyncapi + with: + type: object + title: AsyncApiArguments + description: The Async API call arguments. + properties: + document: + $ref: '#/$defs/externalResource' + title: AsyncAPIDocument + description: The document that defines the AsyncAPI operation to call. + channel: + type: string + title: With + description: The name of the channel on which to perform the operation. Used only in case the referenced document uses AsyncAPI v2.6.0. + operation: + type: string + title: AsyncAPIOperation + description: A reference to the AsyncAPI operation to call. + server: + $ref: '#/$defs/asyncApiServer' + title: AsyncAPIServer + description: An object used to configure to the server to call the specified AsyncAPI operation on. + protocol: + type: string + title: AsyncApiProtocol + description: The protocol to use to select the target server. + enum: [ amqp, amqp1, anypointmq, googlepubsub, http, ibmmq, jms, kafka, mercure, mqtt, mqtt5, nats, pulsar, redis, sns, solace, sqs, stomp, ws ] + message: + $ref: '#/$defs/asyncApiOutboundMessage' + title: AsyncApiMessage + description: An object used to configure the message to publish using the target operation. + subscription: + $ref: '#/$defs/asyncApiSubscription' + title: AsyncApiSubscription + description: An object used to configure the subscription to messages consumed using the target operation. + authentication: + $ref: '#/$defs/referenceableAuthenticationPolicy' + title: AsyncAPIAuthentication + description: The authentication policy, if any, to use when calling the AsyncAPI operation. + oneOf: + - required: [ document, operation, message ] + - required: [ document, operation, subscription ] + - required: [ document, channel, message ] + - required: [ document, channel, subscription ] + unevaluatedProperties: false + - title: CallGRPC + description: Defines the GRPC call to perform. + type: object + unevaluatedProperties: false + required: [ call, with ] + allOf: + - $ref: '#/$defs/taskBase' + - properties: + call: + type: string + const: grpc + with: + type: object + title: GRPCArguments + description: The GRPC call arguments. + properties: + proto: + $ref: '#/$defs/externalResource' + title: WithGRPCProto + description: The proto resource that describes the GRPC service to call. + service: + type: object + title: WithGRPCService + unevaluatedProperties: false + properties: + name: + type: string + title: WithGRPCServiceName + description: The name of the GRPC service to call. + host: + type: string + title: WithGRPCServiceHost + description: The hostname of the GRPC service to call. + pattern: ^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$ + port: + type: integer + title: WithGRPCServicePost + description: The port number of the GRPC service to call. + minimum: 0 + maximum: 65535 + authentication: + $ref: '#/$defs/referenceableAuthenticationPolicy' + title: WithGRPCServiceAuthentication + description: The endpoint's authentication policy, if any. + required: [ name, host ] + method: + type: string + title: WithGRPCMethod + description: The name of the method to call on the defined GRPC service. + arguments: + type: object + title: WithGRPCArguments + description: The arguments, if any, to call the method with. + additionalProperties: true + required: [ proto, service, method ] + unevaluatedProperties: false + - title: CallHTTP + description: Defines the HTTP call to perform. + type: object + unevaluatedProperties: false + required: [ call, with ] + allOf: + - $ref: '#/$defs/taskBase' + - properties: + call: + type: string + const: http + with: + type: object + title: HTTPArguments + description: The HTTP call arguments. + properties: + method: + type: string + title: HTTPMethod + description: The HTTP method of the HTTP request to perform. + endpoint: + title: HTTPEndpoint + description: The HTTP endpoint to send the request to. + $ref: '#/$defs/endpoint' + headers: + oneOf: + - type: object + additionalProperties: + type: string + - $ref: '#/$defs/runtimeExpression' + title: HTTPHeaders + description: A name/value mapping of the headers, if any, of the HTTP request to perform. + body: + title: HTTPBody + description: The body, if any, of the HTTP request to perform. + query: + oneOf: + - type: object + additionalProperties: + type: string + - $ref: '#/$defs/runtimeExpression' + title: HTTPQuery + description: A name/value mapping of the query parameters, if any, of the HTTP request to perform. + additionalProperties: true + output: + type: string + title: HTTPOutput + description: The http call output format. Defaults to 'content'. + enum: [ raw, content, response ] + redirect: + type: boolean + title: HttpRedirect + description: Specifies whether redirection status codes (`300–399`) should be treated as errors. + required: [ method, endpoint ] + unevaluatedProperties: false + - title: CallOpenAPI + description: Defines the OpenAPI call to perform. + type: object + unevaluatedProperties: false + required: [ call, with ] + allOf: + - $ref: '#/$defs/taskBase' + - properties: + call: + type: string + const: openapi + with: + type: object + title: OpenAPIArguments + description: The OpenAPI call arguments. + properties: + document: + $ref: '#/$defs/externalResource' + title: WithOpenAPIDocument + description: The document that defines the OpenAPI operation to call. + operationId: + type: string + title: WithOpenAPIOperation + description: The id of the OpenAPI operation to call. + parameters: + type: object + title: WithOpenAPIParameters + description: A name/value mapping of the parameters of the OpenAPI operation to call. + additionalProperties: true + authentication: + $ref: '#/$defs/referenceableAuthenticationPolicy' + title: WithOpenAPIAuthentication + description: The authentication policy, if any, to use when calling the OpenAPI operation. + output: + type: string + enum: [ raw, content, response ] + title: WithOpenAPIOutput + description: The http call output format. Defaults to 'content'. + redirect: + type: boolean + title: HttpRedirect + description: Specifies whether redirection status codes (`300–399`) should be treated as errors. + required: [ document, operationId ] + unevaluatedProperties: false + - title: CallFunction + description: Defines the function call to perform. + type: object + unevaluatedProperties: false + required: [ call ] + allOf: + - $ref: '#/$defs/taskBase' + - properties: + call: + type: string + not: + enum: ["asyncapi", "grpc", "http", "openapi"] + description: The name of the function to call. + with: + type: object + title: FunctionArguments + description: A name/value mapping of the parameters, if any, to call the function with. + additionalProperties: true + forkTask: + type: object + title: ForkTask + description: Allows workflows to execute multiple tasks concurrently and optionally race them against each other, with a single possible winner, which sets the task's output. + unevaluatedProperties: false + required: [ fork ] + allOf: + - $ref: '#/$defs/taskBase' + - properties: + fork: + type: object + title: ForkTaskConfiguration + description: The configuration of the branches to perform concurrently. + unevaluatedProperties: false + required: [ branches ] + properties: + branches: + $ref: '#/$defs/taskList' + title: ForkBranches + compete: + type: boolean + title: ForkCompete + description: Indicates whether or not the concurrent tasks are racing against each other, with a single possible winner, which sets the composite task's output. + default: false + doTask: + type: object + title: DoTask + description: Allows to execute a list of tasks in sequence. + unevaluatedProperties: false + required: [ do ] + allOf: + - $ref: '#/$defs/taskBase' + - properties: + do: + $ref: '#/$defs/taskList' + title: DoTaskConfiguration + description: The configuration of the tasks to perform sequentially. + emitTask: + type: object + title: EmitTask + description: Allows workflows to publish events to event brokers or messaging systems, facilitating communication and coordination between different components and services. + required: [ emit ] + unevaluatedProperties: false + allOf: + - $ref: '#/$defs/taskBase' + - properties: + emit: + type: object + title: EmitTaskConfiguration + description: The configuration of an event's emission. + unevaluatedProperties: false + properties: + event: + type: object + title: EmitEventDefinition + description: The definition of the event to emit. + properties: + with: + $ref: '#/$defs/eventProperties' + title: EmitEventWith + description: Defines the properties of event to emit. + required: [ source, type ] + additionalProperties: true + required: [ event ] + forTask: + type: object + title: ForTask + description: Allows workflows to iterate over a collection of items, executing a defined set of subtasks for each item in the collection. This task type is instrumental in handling scenarios such as batch processing, data transformation, and repetitive operations across datasets. + required: [ for, do ] + unevaluatedProperties: false + allOf: + - $ref: '#/$defs/taskBase' + - properties: + for: + type: object + title: ForTaskConfiguration + description: The definition of the loop that iterates over a range of values. + unevaluatedProperties: false + properties: + each: + type: string + title: ForEach + description: The name of the variable used to store the current item being enumerated. + default: item + in: + type: string + title: ForIn + description: A runtime expression used to get the collection to enumerate. + at: + type: string + title: ForAt + description: The name of the variable used to store the index of the current item being enumerated. + default: index + required: [ in ] + while: + type: string + title: While + description: A runtime expression that represents the condition, if any, that must be met for the iteration to continue. + do: + $ref: '#/$defs/taskList' + title: ForTaskDo + listenTask: + type: object + title: ListenTask + description: Provides a mechanism for workflows to await and react to external events, enabling event-driven behavior within workflow systems. + required: [ listen ] + unevaluatedProperties: false + allOf: + - $ref: '#/$defs/taskBase' + - properties: + listen: + type: object + title: ListenTaskConfiguration + description: The configuration of the listener to use. + unevaluatedProperties: false + properties: + to: + $ref: '#/$defs/eventConsumptionStrategy' + title: ListenTo + description: Defines the event(s) to listen to. + read: + type: string + enum: [ data, envelope, raw ] + default: data + title: ListenAndReadAs + description: Specifies how events are read during the listen operation. + required: [ to ] + foreach: + $ref: '#/$defs/subscriptionIterator' + title: ListenIterator + description: Configures the iterator, if any, for processing consumed event(s). + raiseTask: + type: object + title: RaiseTask + description: Intentionally triggers and propagates errors. + required: [ raise ] + unevaluatedProperties: false + allOf: + - $ref: '#/$defs/taskBase' + - properties: + raise: + type: object + title: RaiseTaskConfiguration + description: The definition of the error to raise. + unevaluatedProperties: false + properties: + error: + title: RaiseTaskError + oneOf: + - $ref: '#/$defs/error' + title: RaiseErrorDefinition + description: Defines the error to raise. + - type: string + title: RaiseErrorReference + description: The name of the error to raise + required: [ error ] + runTask: + type: object + title: RunTask + description: Provides the capability to execute external containers, shell commands, scripts, or workflows. + required: [ run ] + unevaluatedProperties: false + allOf: + - $ref: '#/$defs/taskBase' + - properties: + run: + type: object + title: RunTaskConfiguration + description: The configuration of the process to execute. + unevaluatedProperties: false + properties: + await: + type: boolean + default: true + title: AwaitProcessCompletion + description: Whether to await the process completion before continuing. + return: + type: string + title: ProcessReturnType + description: Configures the output of the process. + enum: [ stdout, stderr, code, all, none ] + default: stdout + oneOf: + - title: RunContainer + description: Enables the execution of external processes encapsulated within a containerized environment. + properties: + container: + type: object + title: Container + description: The configuration of the container to run. + unevaluatedProperties: false + properties: + image: + type: string + title: ContainerImage + description: The name of the container image to run. + name: + type: string + title: ContainerName + description: A runtime expression, if any, used to give specific name to the container. + command: + type: string + title: ContainerCommand + description: The command, if any, to execute on the container. + ports: + type: object + title: ContainerPorts + description: The container's port mappings, if any. + volumes: + type: object + title: ContainerVolumes + description: The container's volume mappings, if any. + environment: + type: object + title: ContainerEnvironment + description: A key/value mapping of the environment variables, if any, to use when running the configured process. + lifetime: + $ref: '#/$defs/containerLifetime' + title: ContainerLifetime + description: An object, if any, used to configure the container's lifetime + required: [ image ] + required: [ container ] + - title: RunScript + description: Enables the execution of custom scripts or code within a workflow, empowering workflows to perform specialized logic, data processing, or integration tasks by executing user-defined scripts written in various programming languages. + properties: + script: + type: object + title: Script + description: The configuration of the script to run. + unevaluatedProperties: false + properties: + language: + type: string + title: ScriptLanguage + description: The language of the script to run. + arguments: + type: object + title: ScriptArguments + description: A key/value mapping of the arguments, if any, to use when running the configured script. + additionalProperties: true + environment: + type: object + title: ScriptEnvironment + description: A key/value mapping of the environment variables, if any, to use when running the configured script process. + additionalProperties: true + oneOf: + - title: InlineScript + type: object + description: The script's code. + properties: + code: + type: string + title: InlineScriptCode + required: [ code ] + - title: ExternalScript + type: object + description: The script's resource. + properties: + source: + $ref: '#/$defs/externalResource' + title: ExternalScriptResource + required: [ source ] + required: [ language ] + required: [ script ] + - title: RunShell + description: Enables the execution of shell commands within a workflow, enabling workflows to interact with the underlying operating system and perform system-level operations, such as file manipulation, environment configuration, or system administration tasks. + properties: + shell: + type: object + title: Shell + description: The configuration of the shell command to run. + unevaluatedProperties: false + properties: + command: + type: string + title: ShellCommand + description: The shell command to run. + arguments: + type: object + title: ShellArguments + description: A list of the arguments of the shell command to run. + additionalProperties: true + environment: + type: object + title: ShellEnvironment + description: A key/value mapping of the environment variables, if any, to use when running the configured process. + additionalProperties: true + required: [ command ] + required: [ shell ] + - title: RunWorkflow + description: Enables the invocation and execution of nested workflows within a parent workflow, facilitating modularization, reusability, and abstraction of complex logic or business processes by encapsulating them into standalone workflow units. + properties: + workflow: + type: object + title: SubflowConfiguration + description: The configuration of the workflow to run. + unevaluatedProperties: false + properties: + namespace: + type: string + title: SubflowNamespace + description: The namespace the workflow to run belongs to. + name: + type: string + title: SubflowName + description: The name of the workflow to run. + version: + type: string + default: latest + title: SubflowVersion + description: The version of the workflow to run. Defaults to latest. + input: + type: object + title: SubflowInput + description: The data, if any, to pass as input to the workflow to execute. The value should be validated against the target workflow's input schema, if specified. + additionalProperties: true + required: [ namespace, name, version ] + required: [ workflow ] + setTask: + type: object + title: SetTask + description: A task used to set data. + required: [ set ] + unevaluatedProperties: false + allOf: + - $ref: '#/$defs/taskBase' + - properties: + set: + oneOf: + - type: object + minProperties: 1 + additionalProperties: true + - type: string + title: SetTaskConfiguration + description: The data to set. + switchTask: + type: object + title: SwitchTask + description: Enables conditional branching within workflows, allowing them to dynamically select different paths based on specified conditions or criteria. + required: [ switch ] + unevaluatedProperties: false + allOf: + - $ref: '#/$defs/taskBase' + - properties: + switch: + type: array + title: SwitchTaskConfiguration + description: The definition of the switch to use. + minItems: 1 + items: + type: object + title: SwitchItem + minProperties: 1 + maxProperties: 1 + additionalProperties: + type: object + title: SwitchCase + description: The definition of a case within a switch task, defining a condition and corresponding tasks to execute if the condition is met. + unevaluatedProperties: false + required: [ then ] + properties: + when: + type: string + title: SwitchCaseCondition + description: A runtime expression used to determine whether or not the case matches. + then: + $ref: '#/$defs/flowDirective' + title: SwitchCaseOutcome + description: The flow directive to execute when the case matches. + tryTask: + type: object + title: TryTask + description: Serves as a mechanism within workflows to handle errors gracefully, potentially retrying failed tasks before proceeding with alternate ones. + required: [ try, catch ] + unevaluatedProperties: false + allOf: + - $ref: '#/$defs/taskBase' + - properties: + try: + $ref: '#/$defs/taskList' + title: TryTaskConfiguration + description: The task(s) to perform. + catch: + type: object + title: TryTaskCatch + description: The object used to define the errors to catch. + unevaluatedProperties: false + properties: + errors: + type: object + title: CatchErrors + properties: + with: + $ref: '#/$defs/errorFilter' + description: static error filter + as: + type: string + title: CatchAs + description: The name of the runtime expression variable to save the error as. Defaults to 'error'. + when: + type: string + title: CatchWhen + description: A runtime expression used to determine whether to catch the filtered error. + exceptWhen: + type: string + title: CatchExceptWhen + description: A runtime expression used to determine whether not to catch the filtered error. + retry: + oneOf: + - $ref: '#/$defs/retryPolicy' + title: RetryPolicyDefinition + description: The retry policy to use, if any, when catching errors. + - type: string + title: RetryPolicyReference + description: The name of the retry policy to use, if any, when catching errors. + do: + $ref: '#/$defs/taskList' + title: TryTaskCatchDo + description: The definition of the task(s) to run when catching an error. + waitTask: + type: object + title: WaitTask + description: Allows workflows to pause or delay their execution for a specified period of time. + required: [ wait ] + unevaluatedProperties: false + allOf: + - $ref: '#/$defs/taskBase' + - properties: + wait: + $ref: '#/$defs/duration' + title: WaitTaskConfiguration + description: The amount of time to wait. + flowDirective: + title: FlowDirective + description: Represents different transition options for a workflow. + anyOf: + - title: FlowDirectiveEnum + type: string + enum: [ continue, exit, end ] + default: continue + - type: string + referenceableAuthenticationPolicy: + type: object + title: ReferenceableAuthenticationPolicy + description: Represents a referenceable authentication policy. + unevaluatedProperties: false + oneOf: + - title: AuthenticationPolicyReference + description: The reference of the authentication policy to use. + properties: + use: + type: string + minLength: 1 + title: ReferenceableAuthenticationPolicyName + description: The name of the authentication policy to use. + required: [use] + - $ref: '#/$defs/authenticationPolicy' + secretBasedAuthenticationPolicy: + type: object + title: SecretBasedAuthenticationPolicy + description: Represents an authentication policy based on secrets. + unevaluatedProperties: false + properties: + use: + type: string + minLength: 1 + title: SecretBasedAuthenticationPolicyName + description: The name of the authentication policy to use. + required: [use] + authenticationPolicy: + type: object + title: AuthenticationPolicy + description: Defines an authentication policy. + oneOf: + - title: BasicAuthenticationPolicy + description: Use basic authentication. + properties: + basic: + type: object + title: BasicAuthenticationPolicyConfiguration + description: The configuration of the basic authentication policy. + unevaluatedProperties: false + oneOf: + - title: BasicAuthenticationProperties + description: Inline configuration of the basic authentication policy. + properties: + username: + type: string + description: The username to use. + password: + type: string + description: The password to use. + required: [ username, password ] + - $ref: '#/$defs/secretBasedAuthenticationPolicy' + title: BasicAuthenticationPolicySecret + description: Secret based configuration of the basic authentication policy. + required: [ basic ] + - title: BearerAuthenticationPolicy + description: Use bearer authentication. + properties: + bearer: + type: object + title: BearerAuthenticationPolicyConfiguration + description: The configuration of the bearer authentication policy. + unevaluatedProperties: false + oneOf: + - title: BearerAuthenticationProperties + description: Inline configuration of the bearer authentication policy. + properties: + token: + type: string + description: The bearer token to use. + required: [ token ] + - $ref: '#/$defs/secretBasedAuthenticationPolicy' + title: BearerAuthenticationPolicySecret + description: Secret based configuration of the bearer authentication policy. + required: [ bearer ] + - title: DigestAuthenticationPolicy + description: Use digest authentication. + properties: + digest: + type: object + title: DigestAuthenticationPolicyConfiguration + description: The configuration of the digest authentication policy. + unevaluatedProperties: false + oneOf: + - title: DigestAuthenticationProperties + description: Inline configuration of the digest authentication policy. + properties: + username: + type: string + description: The username to use. + password: + type: string + description: The password to use. + required: [ username, password ] + - $ref: '#/$defs/secretBasedAuthenticationPolicy' + title: DigestAuthenticationPolicySecret + description: Secret based configuration of the digest authentication policy. + required: [ digest ] + - title: OAuth2AuthenticationPolicy + description: Use OAuth2 authentication. + properties: + oauth2: + type: object + title: OAuth2AuthenticationPolicyConfiguration + description: The configuration of the OAuth2 authentication policy. + unevaluatedProperties: false + oneOf: + - type: object + title: OAuth2ConnectAuthenticationProperties + description: The inline configuration of the OAuth2 authentication policy. + unevaluatedProperties: false + allOf: + - $ref: '#/$defs/oauth2AuthenticationProperties' + - type: object + properties: + endpoints: + type: object + title: OAuth2AuthenticationPropertiesEndpoints + description: The endpoint configurations for OAuth2. + properties: + token: + type: string + format: uri-template + default: /oauth2/token + title: OAuth2TokenEndpoint + description: The relative path to the token endpoint. Defaults to `/oauth2/token`. + revocation: + type: string + format: uri-template + default: /oauth2/revoke + title: OAuth2RevocationEndpoint + description: The relative path to the revocation endpoint. Defaults to `/oauth2/revoke`. + introspection: + type: string + format: uri-template + default: /oauth2/introspect + title: OAuth2IntrospectionEndpoint + description: The relative path to the introspection endpoint. Defaults to `/oauth2/introspect`. + - $ref: '#/$defs/secretBasedAuthenticationPolicy' + title: OAuth2AuthenticationPolicySecret + description: Secret based configuration of the OAuth2 authentication policy. + required: [ oauth2 ] + - title: OpenIdConnectAuthenticationPolicy + description: Use OpenIdConnect authentication. + properties: + oidc: + type: object + title: OpenIdConnectAuthenticationPolicyConfiguration + description: The configuration of the OpenIdConnect authentication policy. + unevaluatedProperties: false + oneOf: + - $ref: '#/$defs/oauth2AuthenticationProperties' + title: OpenIdConnectAuthenticationProperties + description: The inline configuration of the OpenIdConnect authentication policy. + unevaluatedProperties: false + - $ref: '#/$defs/secretBasedAuthenticationPolicy' + title: OpenIdConnectAuthenticationPolicySecret + description: Secret based configuration of the OpenIdConnect authentication policy. + required: [ oidc ] + oauth2AuthenticationProperties: + type: object + title: OAuth2AutenthicationData + description: Inline configuration of the OAuth2 authentication policy. + properties: + authority: + $ref: '#/$defs/uriTemplate' + title: OAuth2AutenthicationDataAuthority + description: The URI that references the OAuth2 authority to use. + grant: + type: string + enum: [ authorization_code, client_credentials, password, refresh_token, 'urn:ietf:params:oauth:grant-type:token-exchange'] + title: OAuth2AutenthicationDataGrant + description: The grant type to use. + client: + type: object + title: OAuth2AutenthicationDataClient + description: The definition of an OAuth2 client. + unevaluatedProperties: false + properties: + id: + type: string + title: ClientId + description: The client id to use. + secret: + type: string + title: ClientSecret + description: The client secret to use, if any. + assertion: + type: string + title: ClientAssertion + description: A JWT containing a signed assertion with your application credentials. + authentication: + type: string + enum: [ client_secret_basic, client_secret_post, client_secret_jwt, private_key_jwt, none ] + default: client_secret_post + title: ClientAuthentication + description: The authentication method to use to authenticate the client. + request: + type: object + title: OAuth2TokenRequest + description: The configuration of an OAuth2 token request + properties: + encoding: + type: string + enum: [ 'application/x-www-form-urlencoded', 'application/json' ] + default: 'application/x-www-form-urlencoded' + title: Oauth2TokenRequestEncoding + issuers: + type: array + title: OAuth2Issuers + description: A list that contains that contains valid issuers that will be used to check against the issuer of generated tokens. + items: + type: string + scopes: + type: array + title: OAuth2AutenthicationDataScopes + description: The scopes, if any, to request the token for. + items: + type: string + audiences: + type: array + title: OAuth2AutenthicationDataAudiences + description: The audiences, if any, to request the token for. + items: + type: string + username: + type: string + title: OAuth2AutenthicationDataUsername + description: The username to use. Used only if the grant type is Password. + password: + type: string + title: OAuth2AutenthicationDataPassword + description: The password to use. Used only if the grant type is Password. + subject: + $ref: '#/$defs/oauth2Token' + title: OAuth2AutenthicationDataSubject + description: The security token that represents the identity of the party on behalf of whom the request is being made. + actor: + $ref: '#/$defs/oauth2Token' + title: OAuth2AutenthicationDataActor + description: The security token that represents the identity of the acting party. + oauth2Token: + type: object + title: OAuth2TokenDefinition + description: Represents an OAuth2 token. + unevaluatedProperties: false + properties: + token: + type: string + title: OAuth2Token + description: The security token to use. + type: + type: string + title: OAuth2TokenType + description: The type of the security token to use. + required: [ token, type ] + duration: + oneOf: + - type: object + minProperties: 1 + unevaluatedProperties: false + properties: + days: + type: integer + title: DurationDays + description: Number of days, if any. + hours: + type: integer + title: DurationHours + description: Number of days, if any. + minutes: + type: integer + title: DurationMinutes + description: Number of minutes, if any. + seconds: + type: integer + title: DurationSeconds + description: Number of seconds, if any. + milliseconds: + type: integer + title: DurationMilliseconds + description: Number of milliseconds, if any. + title: DurationInline + description: The inline definition of a duration. + - type: string + pattern: '^P(?!$)(\d+(?:\.\d+)?Y)?(\d+(?:\.\d+)?M)?(\d+(?:\.\d+)?W)?(\d+(?:\.\d+)?D)?(T(?=\d)(\d+(?:\.\d+)?H)?(\d+(?:\.\d+)?M)?(\d+(?:\.\d+)?S)?)?$' + title: DurationExpression + description: The ISO 8601 expression of a duration. + error: + type: object + title: Error + description: Represents an error. + unevaluatedProperties: false + properties: + type: + title: ErrorType + description: A URI reference that identifies the error type. + oneOf: + - title: LiteralErrorType + $ref: '#/$defs/uriTemplate' + description: The literal error type. + - title: ExpressionErrorType + $ref: '#/$defs/runtimeExpression' + description: An expression based error type. + status: + type: integer + title: ErrorStatus + description: The status code generated by the origin for this occurrence of the error. + instance: + title: ErrorInstance + description: A JSON Pointer used to reference the component the error originates from. + oneOf: + - title: LiteralErrorInstance + description: The literal error instance. + type: string + format: json-pointer + - $ref: '#/$defs/runtimeExpression' + title: ExpressionErrorInstance + description: An expression based error instance. + title: + description: A short, human-readable summary of the error. + title: ErrorTitle + anyOf: + - $ref: '#/$defs/runtimeExpression' + title: ExpressionErrorTitle + - type: string + title: LiteralErrorTitle + detail: + title: ErrorDetails + description: A human-readable explanation specific to this occurrence of the error. + anyOf: + - $ref: '#/$defs/runtimeExpression' + title: ExpressionErrorDetails + - type: string + title: LiteralErrorDetails + required: [ type, status ] + errorFilter: + type: object + title: ErrorFilter + description: Error filtering base on static values. For error filtering on dynamic values, use catch.when property + minProperties: 1 + properties: + type: + type: string + description: if present, means this value should be used for filtering + status: + type: integer + description: if present, means this value should be used for filtering + instance: + type: string + description: if present, means this value should be used for filtering + title: + type: string + description: if present, means this value should be used for filtering + details: + type: string + description: if present, means this value should be used for filtering + uriTemplate: + title: UriTemplate + anyOf: + - title: LiteralUriTemplate + type: string + format: uri-template + pattern: "^[A-Za-z][A-Za-z0-9+\\-.]*://.*" + - title: LiteralUri + type: string + format: uri + pattern: "^[A-Za-z][A-Za-z0-9+\\-.]*://.*" + endpoint: + title: Endpoint + description: Represents an endpoint. + oneOf: + - $ref: '#/$defs/runtimeExpression' + - $ref: '#/$defs/uriTemplate' + - title: EndpointConfiguration + type: object + unevaluatedProperties: false + properties: + uri: + title: EndpointUri + description: The endpoint's URI. + oneOf: + - title: LiteralEndpointURI + description: The literal endpoint's URI. + $ref: '#/$defs/uriTemplate' + - title: ExpressionEndpointURI + $ref: '#/$defs/runtimeExpression' + description: An expression based endpoint's URI. + authentication: + $ref: '#/$defs/referenceableAuthenticationPolicy' + title: EndpointAuthentication + description: The authentication policy to use. + required: [ uri ] + eventProperties: + type: object + title: EventProperties + description: Describes the properties of an event. + properties: + id: + type: string + title: EventId + description: The event's unique identifier. + source: + title: EventSource + description: Identifies the context in which an event happened. + oneOf: + - $ref: '#/$defs/uriTemplate' + - $ref: '#/$defs/runtimeExpression' + type: + type: string + title: EventType + description: This attribute contains a value describing the type of event related to the originating occurrence. + time: + title: EventTime + description: When the event occured. + oneOf: + - title: LiteralTime + type: string + format: date-time + - $ref: '#/$defs/runtimeExpression' + subject: + type: string + title: EventSubject + description: The subject of the event. + datacontenttype: + type: string + title: EventDataContentType + description: Content type of data value. This attribute enables data to carry any type of content, whereby format and encoding might differ from that of the chosen event format. + dataschema: + title: EventDataschema + description: The schema describing the event format. + oneOf: + - title: LiteralDataSchema + $ref: '#/$defs/uriTemplate' + description: The literal event data schema. + - title: ExpressionDataSchema + $ref: '#/$defs/runtimeExpression' + description: An expression based event data schema. + data: + title: EventData + description: The event's payload data + anyOf: + - $ref: '#/$defs/runtimeExpression' + - {} + additionalProperties: true + eventConsumptionStrategy: + type: object + title: EventConsumptionStrategy + description: Describe the event consumption strategy to adopt. + unevaluatedProperties: false + oneOf: + - title: AllEventConsumptionStrategy + properties: + all: + type: array + title: AllEventConsumptionStrategyConfiguration + description: A list containing all the events that must be consumed. + items: + $ref: '#/$defs/eventFilter' + required: [ all ] + - title: AnyEventConsumptionStrategy + properties: + any: + type: array + title: AnyEventConsumptionStrategyConfiguration + description: A list containing any of the events to consume. + items: + $ref: '#/$defs/eventFilter' + until: + oneOf: + - type: string + title: AnyEventUntilCondition + description: A runtime expression condition evaluated after consuming an event and which determines whether or not to continue listening. + - allOf: + - $ref: '#/$defs/eventConsumptionStrategy' + description: The strategy that defines the event(s) to consume to stop listening. + - properties: + until: false + title: AnyEventUntilConsumed + required: [ any ] + - title: OneEventConsumptionStrategy + properties: + one: + $ref: '#/$defs/eventFilter' + title: OneEventConsumptionStrategyConfiguration + description: The single event to consume. + required: [ one ] + eventFilter: + type: object + title: EventFilter + description: An event filter is a mechanism used to selectively process or handle events based on predefined criteria, such as event type, source, or specific attributes. + unevaluatedProperties: false + properties: + with: + $ref: '#/$defs/eventProperties' + minProperties: 1 + title: WithEvent + description: An event filter is a mechanism used to selectively process or handle events based on predefined criteria, such as event type, source, or specific attributes. + correlate: + type: object + title: EventFilterCorrelate + description: A correlation is a link between events and data, established by mapping event attributes to specific data attributes, allowing for coordinated processing or handling based on event characteristics. + additionalProperties: + type: object + properties: + from: + type: string + title: CorrelateFrom + description: A runtime expression used to extract the correlation value from the filtered event. + expect: + type: string + title: CorrelateExpect + description: A constant or a runtime expression, if any, used to determine whether or not the extracted correlation value matches expectations. If not set, the first extracted value will be used as the correlation's expectation. + required: [ from ] + required: [ with ] + extension: + type: object + title: Extension + description: The definition of an extension. + unevaluatedProperties: false + properties: + extend: + type: string + enum: [ call, composite, emit, for, listen, raise, run, set, switch, try, wait, all ] + title: ExtensionTarget + description: The type of task to extend. + when: + type: string + title: ExtensionCondition + description: A runtime expression, if any, used to determine whether or not the extension should apply in the specified context. + before: + $ref: '#/$defs/taskList' + title: ExtensionDoBefore + description: The task(s) to execute before the extended task, if any. + after: + $ref: '#/$defs/taskList' + title: ExtensionDoAfter + description: The task(s) to execute after the extended task, if any. + required: [ extend ] + externalResource: + type: object + title: ExternalResource + description: Represents an external resource. + unevaluatedProperties: false + properties: + name: + type: string + title: ExternalResourceName + description: The name of the external resource, if any. + endpoint: + $ref: '#/$defs/endpoint' + title: ExternalResourceEndpoint + description: The endpoint of the external resource. + required: [ endpoint ] + input: + type: object + title: Input + description: Configures the input of a workflow or task. + unevaluatedProperties: false + properties: + schema: + $ref: '#/$defs/schema' + title: InputSchema + description: The schema used to describe and validate the input of the workflow or task. + from: + title: InputFrom + description: A runtime expression, if any, used to mutate and/or filter the input of the workflow or task. + oneOf: + - type: string + - type: object + output: + type: object + title: Output + description: Configures the output of a workflow or task. + unevaluatedProperties: false + properties: + schema: + $ref: '#/$defs/schema' + title: OutputSchema + description: The schema used to describe and validate the output of the workflow or task. + as: + title: OutputAs + description: A runtime expression, if any, used to mutate and/or filter the output of the workflow or task. + oneOf: + - type: string + - type: object + export: + type: object + title: Export + description: Set the content of the context. . + unevaluatedProperties: false + properties: + schema: + $ref: '#/$defs/schema' + title: ExportSchema + description: The schema used to describe and validate the workflow context. + as: + title: ExportAs + description: A runtime expression, if any, used to export the output data to the context. + oneOf: + - type: string + - type: object + retryPolicy: + type: object + title: RetryPolicy + description: Defines a retry policy. + unevaluatedProperties: false + properties: + when: + type: string + title: RetryWhen + description: A runtime expression, if any, used to determine whether or not to retry running the task, in a given context. + exceptWhen: + type: string + title: RetryExcepWhen + description: A runtime expression used to determine whether or not to retry running the task, in a given context. + delay: + $ref: '#/$defs/duration' + title: RetryDelay + description: The duration to wait between retry attempts. + backoff: + type: object + title: RetryBackoff + description: The retry duration backoff. + unevaluatedProperties: false + oneOf: + - title: ConstantBackoff + properties: + constant: + type: object + description: The definition of the constant backoff to use, if any. + required: [ constant ] + - title: ExponentialBackOff + properties: + exponential: + type: object + description: The definition of the exponential backoff to use, if any. + required: [ exponential ] + - title: LinearBackoff + properties: + linear: + type: object + description: The definition of the linear backoff to use, if any. + required: [ linear ] + limit: + type: object + title: RetryLimit + unevaluatedProperties: false + properties: + attempt: + type: object + title: RetryLimitAttempt + unevaluatedProperties: false + properties: + count: + type: integer + title: RetryLimitAttemptCount + description: The maximum amount of retry attempts, if any. + duration: + $ref: '#/$defs/duration' + title: RetryLimitAttemptDuration + description: The maximum duration for each retry attempt. + duration: + $ref: '#/$defs/duration' + title: RetryLimitDuration + description: The duration limit, if any, for all retry attempts. + description: The retry limit, if any. + jitter: + type: object + title: RetryPolicyJitter + description: The parameters, if any, that control the randomness or variability of the delay between retry attempts. + unevaluatedProperties: false + properties: + from: + $ref: '#/$defs/duration' + title: RetryPolicyJitterFrom + description: The minimum duration of the jitter range. + to: + $ref: '#/$defs/duration' + title: RetryPolicyJitterTo + description: The maximum duration of the jitter range. + required: [ from, to ] + schema: + type: object + title: Schema + description: Represents the definition of a schema. + unevaluatedProperties: false + properties: + format: + type: string + default: json + title: SchemaFormat + description: The schema's format. Defaults to 'json'. The (optional) version of the format can be set using `{format}:{version}`. + oneOf: + - title: SchemaInline + properties: + document: + description: The schema's inline definition. + required: [ document ] + - title: SchemaExternal + properties: + resource: + $ref: '#/$defs/externalResource' + title: SchemaExternalResource + description: The schema's external resource. + required: [ resource ] + timeout: + type: object + title: Timeout + description: The definition of a timeout. + unevaluatedProperties: false + properties: + after: + $ref: '#/$defs/duration' + title: TimeoutAfter + description: The duration after which to timeout. + required: [ after ] + catalog: + type: object + title: Catalog + description: The definition of a resource catalog. + unevaluatedProperties: false + properties: + endpoint: + $ref: '#/$defs/endpoint' + title: CatalogEndpoint + description: The root URL where the catalog is hosted. + required: [ endpoint ] + runtimeExpression: + type: string + title: RuntimeExpression + description: A runtime expression. + pattern: "^\\s*\\$\\{.+\\}\\s*$" + containerLifetime: + type: object + title: ContainerLifetime + description: The configuration of a container's lifetime + unevaluatedProperties: false + properties: + cleanup: + type: string + title: ContainerCleanupPolicy + description: The container cleanup policy to use + enum: [ always, never, eventually ] + default: never + after: + $ref: '#/$defs/duration' + title: ContainerLifetimeDuration + description: The duration after which to cleanup the container, in case the cleanup policy has been set to 'eventually' + required: [ cleanup ] + if: + properties: + cleanup: + const: eventually + then: + required: [ after ] + else: + not: + required: [ after ] + processResult: + type: object + title: ProcessResult + description: The object returned by a run task when its return type has been set 'all'. + unevaluatedProperties: false + properties: + code: + type: integer + title: ProcessExitCode + description: The process's exit code. + stdout: + type: string + title: ProcessStandardOutput + description: The content of the process's STDOUT. + stderr: + type: string + title: ProcessStandardError + description: The content of the process's STDERR. + required: [ code, stdout, stderr ] + asyncApiServer: + type: object + title: AsyncApiServer + description: Configures the target server of an AsyncAPI operation. + unevaluatedProperties: false + properties: + name: + type: string + title: AsyncApiServerName + description: The target server's name. + variables: + type: object + title: AsyncApiServerVariables + description: The target server's variables, if any. + required: [ name ] + asyncApiOutboundMessage: + type: object + title: AsyncApiOutboundMessage + description: An object used to configure the message to publish using the target operation. + unevaluatedProperties: false + properties: + payload: + type: object + title: AsyncApiMessagePayload + description: The message's payload, if any. + additionalProperties: true + headers: + type: object + title: AsyncApiMessageHeaders + description: The message's headers, if any. + additionalProperties: true + asyncApiInboundMessage: + type: object + title: AsyncApiInboundMessage + description: Represents a message counsumed by an AsyncAPI subscription. + allOf: + - $ref: '#/$defs/asyncApiOutboundMessage' + properties: + correlationId: + type: string + title: AsyncApiMessageCorrelationId + description: The message's correlation id, if any. + asyncApiSubscription: + type: object + title: AsyncApiSubscription + description: An object used to configure the subscription to messages consumed using the target operation. + unevaluatedProperties: false + properties: + filter: + $ref: '#/$defs/runtimeExpression' + title: AsyncApiSubscriptionCorrelation + description: A runtime expression, if any, used to filter consumed messages. + consume: + $ref: '#/$defs/asyncApiMessageConsumptionPolicy' + title: AsyncApiMessageConsumptionPolicy + description: An object used to configure the subscription's message consumption policy. + foreach: + $ref: '#/$defs/subscriptionIterator' + title: AsyncApiSubscriptionIterator + description: Configures the iterator, if any, for processing consumed messages(s). + required: [ consume ] + asyncApiMessageConsumptionPolicy: + type: object + title: AsyncApiMessageConsumptionPolicy + description: An object used to configure a subscription's message consumption policy. + unevaluatedProperties: false + properties: + for: + $ref: '#/$defs/duration' + title: AsyncApiMessageConsumptionPolicyFor + description: Specifies the time period over which messages will be consumed. + oneOf: + - properties: + amount: + type: integer + description: The amount of (filtered) messages to consume before disposing of the subscription. + title: AsyncApiMessageConsumptionPolicyAmount + required: [ amount ] + - properties: + while: + $ref: '#/$defs/runtimeExpression' + description: A runtime expression evaluated after each consumed (filtered) message to decide if message consumption should continue. + title: AsyncApiMessageConsumptionPolicyWhile + required: [ while ] + - properties: + until: + $ref: '#/$defs/runtimeExpression' + description: A runtime expression evaluated before each consumed (filtered) message to decide if message consumption should continue. + title: AsyncApiMessageConsumptionPolicyUntil + required: [ until ] + subscriptionIterator: + type: object + title: SubscriptionIterator + description: Configures the iteration over each item (event or message) consumed by a subscription. + unevaluatedProperties: false + properties: + item: + type: string + title: SubscriptionIteratorItem + description: The name of the variable used to store the current item being enumerated. + default: item + at: + type: string + title: SubscriptionIteratorIndex + description: The name of the variable used to store the index of the current item being enumerated. + default: index + do: + $ref: '#/$defs/taskList' + title: SubscriptionIteratorTasks + description: The tasks to perform for each consumed item. + output: + $ref: '#/$defs/output' + title: SubscriptionIteratorOutput + description: An object, if any, used to customize the item's output and to document its schema. + export: + $ref: '#/$defs/export' + title: SubscriptionIteratorExport + description: An object, if any, used to customize the content of the workflow context. \ No newline at end of file diff --git a/validation/.gitignore b/validation/.gitignore deleted file mode 100644 index d4dfde66..00000000 --- a/validation/.gitignore +++ /dev/null @@ -1,31 +0,0 @@ -HELP.md -target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/** -!**/src/test/** - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ -build/ - -### VS Code ### -.vscode/ \ No newline at end of file diff --git a/validation/pom.xml b/validation/pom.xml deleted file mode 100644 index 54d01928..00000000 --- a/validation/pom.xml +++ /dev/null @@ -1,86 +0,0 @@ - - 4.0.0 - - - io.serverlessworkflow - serverlessworkflow-parent - 3.0.0-SNAPSHOT - - - serverlessworkflow-validation - Serverless Workflow :: Validation - ${project.parent.version} - jar - Java SDK for Serverless Workflow Specification - - - - org.slf4j - slf4j-api - - - org.slf4j - jcl-over-slf4j - - - - io.serverlessworkflow - serverlessworkflow-api - ${project.version} - - - - org.apache.commons - commons-lang3 - - - - com.github.erosb - everit-json-schema - - - - - org.junit.jupiter - junit-jupiter-api - test - - - org.junit.jupiter - junit-jupiter-engine - test - - - org.junit.jupiter - junit-jupiter-params - test - - - org.mockito - mockito-core - test - - - ch.qos.logback - logback-classic - test - - - org.assertj - assertj-core - test - - - org.hamcrest - hamcrest-library - test - - - org.skyscreamer - jsonassert - test - - - diff --git a/validation/src/main/java/io/serverlessworkflow/validation/WorkflowValidatorImpl.java b/validation/src/main/java/io/serverlessworkflow/validation/WorkflowValidatorImpl.java deleted file mode 100644 index c6314d19..00000000 --- a/validation/src/main/java/io/serverlessworkflow/validation/WorkflowValidatorImpl.java +++ /dev/null @@ -1,426 +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.validation; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import io.serverlessworkflow.api.Workflow; -import io.serverlessworkflow.api.actions.Action; -import io.serverlessworkflow.api.branches.Branch; -import io.serverlessworkflow.api.events.EventDefinition; -import io.serverlessworkflow.api.events.OnEvents; -import io.serverlessworkflow.api.functions.FunctionDefinition; -import io.serverlessworkflow.api.interfaces.WorkflowValidator; -import io.serverlessworkflow.api.states.*; -import io.serverlessworkflow.api.switchconditions.EventCondition; -import io.serverlessworkflow.api.validation.ValidationError; -import io.serverlessworkflow.api.validation.WorkflowSchemaLoader; -import org.everit.json.schema.Schema; -import org.everit.json.schema.ValidationException; -import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.validation.Validation; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -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 String source; - private Workflow workflow; - - @Override - public WorkflowValidator setWorkflow(Workflow workflow) { - this.workflow = workflow; - return this; - } - - @Override - public WorkflowValidator setSource(String source) { - this.source = source; - return this; - } - - @Override - public List validate() { - validationErrors.clear(); - 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"))) { - addValidationError(m, - ValidationError.SCHEMA_VALIDATION); - }}); - - } - } - } catch (Exception e) { - logger.error("Schema validation exception: " + e.getMessage()); - } - } - - // if there are schema validation errors - // 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; - - List events = workflow.getEvents() != null ? workflow.getEvents().getEventDefs() : null; - - if (workflow.getId() == null || workflow.getId().trim().isEmpty()) { - addValidationError("Workflow id should not be empty", - ValidationError.WORKFLOW_VALIDATION); - } - - 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.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); - } - - 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; - if (operationState.getActions() == null || operationState.getActions().size() < 1) { - addValidationError("Operation State has no actions defined", - ValidationError.WORKFLOW_VALIDATION); - } - - 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); - } - } - - 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 (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 (onEvents.getActions() == null || onEvents.getActions().size() < 1) { - addValidationError("Event State eventsActions has no actions", - ValidationError.WORKFLOW_VALIDATION); - } - - List eventRefs = onEvents.getEventRefs(); - if (eventRefs == null || eventRefs.size() < 1) { - addValidationError("Event State eventsActions has no event refs", - 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 (switchState.getDefault() == 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 (s instanceof DelayState) { - DelayState delayState = (DelayState) s; - if (delayState.getTimeDelay() == null || delayState.getTimeDelay().length() < 1) { - addValidationError("Delay state should have a non-empty time delay", - ValidationError.WORKFLOW_VALIDATION); - } - } - - if (s instanceof ParallelState) { - ParallelState parallelState = (ParallelState) s; - if (parallelState.getBranches() == null || parallelState.getBranches().size() < 1) { - addValidationError("Parallel state should have branches", - ValidationError.WORKFLOW_VALIDATION); - } - - List branches = parallelState.getBranches(); - for (Branch branch : branches) { - if ((branch.getActions() == null || branch.getActions().size() < 1) - && (branch.getWorkflowId() == null || branch.getWorkflowId().length() < 1)) { - addValidationError("Parallel state should define either actions or workflow id", - ValidationError.WORKFLOW_VALIDATION); - } - } - } - - if (s instanceof SubflowState) { - SubflowState subflowState = (SubflowState) s; - if (subflowState.getWorkflowId() == null || subflowState.getWorkflowId().isEmpty()) { - addValidationError("SubflowState should have a valid workflow id", - ValidationError.WORKFLOW_VALIDATION); - } - } - - if (s instanceof InjectState) { - InjectState injectState = (InjectState) s; - if (injectState.getData() == null) { - addValidationError("InjectState should have non-null data", - ValidationError.WORKFLOW_VALIDATION); - } - } - - if (s instanceof ForEachState) { - ForEachState forEachState = (ForEachState) s; - if (forEachState.getInputCollection() == null || forEachState.getInputCollection().isEmpty()) { - addValidationError("ForEach state should have a valid inputCollection", - ValidationError.WORKFLOW_VALIDATION); - } - - if (forEachState.getIterationParam() == null || forEachState.getIterationParam().isEmpty()) { - addValidationError("ForEach state should have a valid iteration parameter", - ValidationError.WORKFLOW_VALIDATION); - } - - if ((forEachState.getActions() == null || forEachState.getActions().size() < 1) - && (forEachState.getWorkflowId() == null || forEachState.getWorkflowId().length() < 1)) { - addValidationError("ForEach state should define either actions or workflow id", - ValidationError.WORKFLOW_VALIDATION); - } - } - - 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 (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); - } - } - - - return validationErrors; - } - } - - @Override - public boolean isValid() { - return validate().size() < 1; - } - - @Override - public WorkflowValidator setSchemaValidationEnabled(boolean schemaValidationEnabled) { - this.schemaValidationEnabled = schemaValidationEnabled; - return this; - } - - @Override - public WorkflowValidator reset() { - workflow = null; - validationErrors.clear(); - schemaValidationEnabled = true; - return this; - } - - private boolean haveFunctionDefinition(String functionName, List functions) { - if (functions != null) { - FunctionDefinition fun = functions.stream().filter(f -> f.getName().equals(functionName)) - .findFirst() - .orElse(null); - - return fun == null ? false : true; - } else { - return false; - } - } - - private boolean haveEventsDefinition(String eventName, List events) { - 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 void addValidationError(String message, - String type) { - ValidationError mainError = new ValidationError(); - mainError.setMessage(message); - mainError.setType(type); - validationErrors.add(mainError); - } - - 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("State does not have an unique name: " + name, - ValidationError.WORKFLOW_VALIDATION); - } else { - states.add(name); - } - } - - void addEndState() { - endStates++; - } - } -} diff --git a/validation/src/main/resources/META-INF/services/io.serverlessworkflow.api.interfaces.WorkflowValidator b/validation/src/main/resources/META-INF/services/io.serverlessworkflow.api.interfaces.WorkflowValidator deleted file mode 100644 index cebff91f..00000000 --- a/validation/src/main/resources/META-INF/services/io.serverlessworkflow.api.interfaces.WorkflowValidator +++ /dev/null @@ -1 +0,0 @@ -io.serverlessworkflow.validation.WorkflowValidatorImpl \ No newline at end of file diff --git a/validation/src/test/java/io/serverlessworkflow/validation/test/WorkflowValidationTest.java b/validation/src/test/java/io/serverlessworkflow/validation/test/WorkflowValidationTest.java deleted file mode 100644 index daace669..00000000 --- a/validation/src/test/java/io/serverlessworkflow/validation/test/WorkflowValidationTest.java +++ /dev/null @@ -1,139 +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.validation.test; - -import io.serverlessworkflow.api.Workflow; -import io.serverlessworkflow.api.end.End; -import io.serverlessworkflow.api.interfaces.WorkflowValidator; -import io.serverlessworkflow.api.start.Start; -import io.serverlessworkflow.api.states.DelayState; -import io.serverlessworkflow.api.validation.ValidationError; -import io.serverlessworkflow.validation.WorkflowValidatorImpl; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.List; - -import static io.serverlessworkflow.api.states.DefaultState.Type.DELAY; - -public class WorkflowValidationTest { - - @Test - public void testIncompleteJsonWithSchemaValidation() { - WorkflowValidator workflowValidator = new WorkflowValidatorImpl(); - List validationErrors = workflowValidator.setSource("{\n" + - " \"id\": \"abc\" \n" + - "}").validate(); - Assertions.assertNotNull(validationErrors); - Assertions.assertEquals(3, validationErrors.size()); - } - - @Test - public void testIncompleteYamlWithSchemaValidation() { - WorkflowValidator workflowValidator = new WorkflowValidatorImpl(); - List validationErrors = workflowValidator.setSource("---\n" + - "id: abc\n").validate(); - Assertions.assertNotNull(validationErrors); - Assertions.assertEquals(3, validationErrors.size()); - } - - @Test - public void testFromIncompleteWorkflow() { - Workflow workflow = new Workflow().withId("test-workflow").withVersion("1.0") - .withStart( - new Start() - ) - .withStates(Arrays.asList( - new DelayState().withName("delayState").withType(DELAY) - .withEnd( - new End() - ) - .withTimeDelay("PT1M") - ) - ); - - WorkflowValidator workflowValidator = new WorkflowValidatorImpl(); - List validationErrors = workflowValidator.setWorkflow(workflow).validate(); - Assertions.assertNotNull(validationErrors); - Assertions.assertEquals(1, validationErrors.size()); - - Assertions.assertEquals("Workflow name should not be empty", validationErrors.get(0).getMessage()); - } - - @Test - public void testWorkflowMissingStates() { - WorkflowValidator workflowValidator = new WorkflowValidatorImpl(); - List validationErrors = workflowValidator.setSource("{\n" + - "\t\"id\": \"testwf\",\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("No states found", validationErrors.get(0).getMessage()); - } - - @Test - public void testOperationStateNoFunctionRef() { - 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" + - " \"iterationParam\": \"${ .singlemessage }\",\n" + - " \"workflowId\": \"sendMessageWorkflowId\",\n" + - " \"end\": {\n" + - " \"kind\": \"default\"\n" + - " }\n" + - " }\n" + - "]\n" + - "}").validate(); - Assertions.assertNotNull(validationErrors); - Assertions.assertEquals(1, validationErrors.size()); - - Assertions.assertEquals("Operation State action functionRef does not reference an existing workflow function definition", validationErrors.get(0).getMessage()); - } -} 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