From 32b16ec58a397c9a68526ba99f58169aecabd5ff Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti Date: Fri, 15 Mar 2024 15:04:52 +0100 Subject: [PATCH 1/5] [KOGITO-9454] Adding Java embedded flows to examples --- serverless-workflow-examples/pom.xml | 1 + .../sonata-workflow-fluent/pom.xml | 50 +++++++++ .../examples/ConcatenationExample.java | 67 ++++++++++++ .../workflow/examples/ForEachJavaExample.java | 81 ++++++++++++++ .../workflow/examples/HelloWorld.java | 50 +++++++++ .../workflow/examples/JQInterpolation.java | 51 +++++++++ .../workflow/examples/ParallelRest.java | 102 ++++++++++++++++++ .../src/main/resources/division.sw.json | 26 +++++ .../src/main/resources/expression.sw.json | 73 +++++++++++++ .../src/main/resources/message.txt | 1 + .../src/main/resources/schema/complex.json | 12 +++ .../src/main/resources/schema/expression.json | 15 +++ .../src/main/resources/schema/result.json | 13 +++ .../examples/WorkflowFluentExamplesTest.java | 100 +++++++++++++++++ 14 files changed, 642 insertions(+) create mode 100644 serverless-workflow-examples/sonata-workflow-fluent/pom.xml create mode 100644 serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ConcatenationExample.java create mode 100644 serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ForEachJavaExample.java create mode 100644 serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/HelloWorld.java create mode 100644 serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/JQInterpolation.java create mode 100644 serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ParallelRest.java create mode 100644 serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/division.sw.json create mode 100644 serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/expression.sw.json create mode 100644 serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/message.txt create mode 100644 serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/schema/complex.json create mode 100644 serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/schema/expression.json create mode 100644 serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/schema/result.json create mode 100644 serverless-workflow-examples/sonata-workflow-fluent/src/test/java/org/kie/kogito/serverless/workflow/examples/WorkflowFluentExamplesTest.java diff --git a/serverless-workflow-examples/pom.xml b/serverless-workflow-examples/pom.xml index 0980443157..add587fafc 100644 --- a/serverless-workflow-examples/pom.xml +++ b/serverless-workflow-examples/pom.xml @@ -78,6 +78,7 @@ serverless-workflow-timeouts-showcase-extended serverless-workflow-timeouts-showcase-operator-devprofile serverless-workflow-python-quarkus + sonata-workflow-fluent diff --git a/serverless-workflow-examples/sonata-workflow-fluent/pom.xml b/serverless-workflow-examples/sonata-workflow-fluent/pom.xml new file mode 100644 index 0000000000..5fc537c5c0 --- /dev/null +++ b/serverless-workflow-examples/sonata-workflow-fluent/pom.xml @@ -0,0 +1,50 @@ + + 4.0.0 + + org.kie.kogito.examples + serverless-workflow-examples + 999-SNAPSHOT + + sonata-workflow-fluent + Kogito Example :: Sonata Workflow:: Java Embedded examples + 2.0.0-SNAPSHOT + + org.kie.kogito + kogito-bom + 999-SNAPSHOT + 17 + ${version.surefire.plugin} + + + + + ${kogito.bom.group-id} + ${kogito.bom.artifact-id} + ${kogito.bom.version} + pom + import + + + + + + org.kie.kogito + kogito-serverless-workflow-executor + pom + + + org.slf4j + slf4j-simple + + + org.assertj + assertj-core + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + \ No newline at end of file diff --git a/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ConcatenationExample.java b/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ConcatenationExample.java new file mode 100644 index 0000000000..477a781a5f --- /dev/null +++ b/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ConcatenationExample.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.serverless.workflow.examples; + +import java.util.Map; + +import org.kie.kogito.process.Process; +import org.kie.kogito.serverless.workflow.actions.WorkflowLogLevel; +import org.kie.kogito.serverless.workflow.executor.StaticWorkflowApplication; +import org.kie.kogito.serverless.workflow.models.JsonNodeModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.serverlessworkflow.api.Workflow; + +import static org.kie.kogito.serverless.workflow.fluent.ActionBuilder.call; +import static org.kie.kogito.serverless.workflow.fluent.ActionBuilder.log; +import static org.kie.kogito.serverless.workflow.fluent.FunctionBuilder.expr; +import static org.kie.kogito.serverless.workflow.fluent.StateBuilder.operation; +import static org.kie.kogito.serverless.workflow.fluent.WorkflowBuilder.workflow; + +public class ConcatenationExample { + + private static final Logger logger = LoggerFactory.getLogger(ConcatenationExample.class); + + public static void main(String[] args) { + try (StaticWorkflowApplication application = StaticWorkflowApplication.create()) { + // This flow illustrate the usage of two consecutive function calls + // create a reusable process for several executions + Process process = application.process(getWorkflow()); + // execute it with one person name + logger.info(application.execute(process, Map.of("name", "Javier", "surname", "Tirado")).getWorkflowdata().toPrettyString()); + // execute it with other person name + logger.info(application.execute(process, Map.of("name", "Mark", "surname", "Proctor")).getWorkflowdata().toPrettyString()); + } + } + + static Workflow getWorkflow() { + return workflow("ExpressionExample") + // concatenate name + .start(operation() + .action(call(expr("name", "\"My name is \"+.name"))) + // you can add several sequential actions into an operation + .action(log(WorkflowLogLevel.DEBUG, "\"Response is\"+.response"))) + // concatenate surname + .next(operation() + .action(call(expr("surname", ".response+\" and my surname is \"+.surname"))) + .outputFilter(".response")) + .end().build(); + } +} diff --git a/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ForEachJavaExample.java b/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ForEachJavaExample.java new file mode 100644 index 0000000000..8b9792a98c --- /dev/null +++ b/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ForEachJavaExample.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.serverless.workflow.examples; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +import org.kie.kogito.serverless.workflow.executor.StaticWorkflowApplication; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.serverlessworkflow.api.Workflow; + +import static org.kie.kogito.serverless.workflow.fluent.ActionBuilder.call; +import static org.kie.kogito.serverless.workflow.fluent.FunctionBuilder.expr; +import static org.kie.kogito.serverless.workflow.fluent.FunctionBuilder.java; +import static org.kie.kogito.serverless.workflow.fluent.StateBuilder.forEach; +import static org.kie.kogito.serverless.workflow.fluent.StateBuilder.operation; +import static org.kie.kogito.serverless.workflow.fluent.WorkflowBuilder.workflow; + +public class ForEachJavaExample { + + private static final Logger logger = LoggerFactory.getLogger(ForEachJavaExample.class); + + public static void main(String[] args) { + try (StaticWorkflowApplication application = StaticWorkflowApplication.create()) { + // execute the flow passing the list of names and the file name + logger.info(application.execute(getWorkflow(), Map.of("names", Arrays.asList("Javi", "Mark", "Kris", "Alessandro"), "fileName", "message.txt")).getWorkflowdata().toPrettyString()); + } + } + + static Workflow getWorkflow() { + // this flow illustrate the usage of foreach and how to use java to perform task that are not part of sw spec. + // The flow accepts a list of names and suffix them with a message read from a file + return workflow("ForEachExample") + // first load the message from the file and store it in message property + .start(operation().action(call(java("getMessage", ForEachJavaExample::addAdvice), ".fileName"))) + // then for each element in input names concatenate it with that message + .next(forEach(".names").loopVar("name").outputCollection(".messages") + // jq expression that suffix each name with the message retrieved from the file + .action(call(expr("concat", ".name+.adviceMessage"))) + // only return messages list as result of the flow + .outputFilter("{messages}")) + .end().build(); + } + + // Java method invoked from workflow accepts one parameter, which might be a Map or a primitive/wrapper type, depending on the args provided in the flow + // In this case, we are passing the name of a file in the classpath, so the argument is a string + // Java method return type is always a Map (if not output,it should return an empty map). In this case, + // we are adding an advice message to the flow model read from the file. If the file cannot be read, we return empty map. + private static Map addAdvice(String fileName) { + try (InputStream is = ClassLoader.getSystemResourceAsStream(fileName)) { + if (is != null) { + return Collections.singletonMap("adviceMessage", new String(is.readAllBytes())); + } + } catch (IOException io) { + logger.warn("Error reading file " + fileName + " from classpath", io); + } + return Collections.emptyMap(); + } + +} diff --git a/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/HelloWorld.java b/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/HelloWorld.java new file mode 100644 index 0000000000..3d8648e93a --- /dev/null +++ b/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/HelloWorld.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.serverless.workflow.examples; + +import java.util.Collections; + +import org.kie.kogito.serverless.workflow.executor.StaticWorkflowApplication; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.serverlessworkflow.api.Workflow; + +import static org.kie.kogito.serverless.workflow.fluent.StateBuilder.inject; +import static org.kie.kogito.serverless.workflow.fluent.WorkflowBuilder.jsonObject; +import static org.kie.kogito.serverless.workflow.fluent.WorkflowBuilder.workflow; + +public class HelloWorld { + + private static final Logger logger = LoggerFactory.getLogger(HelloWorld.class); + + public static void main(String[] args) { + try (StaticWorkflowApplication application = StaticWorkflowApplication.create()) { + logger.info("Workflow execution result is {}", application.execute(getWorkflow(), Collections.emptyMap()).getWorkflowdata()); + } + } + + static Workflow getWorkflow() { + return workflow("HelloWorld").start( + inject( + jsonObject().put("greeting", "Hello World").put("mantra", "Serverless Workflow is awesome!"))) + .end() + .build(); + } +} diff --git a/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/JQInterpolation.java b/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/JQInterpolation.java new file mode 100644 index 0000000000..b090d23d23 --- /dev/null +++ b/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/JQInterpolation.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.serverless.workflow.examples; + +import java.io.IOException; + +import org.kie.kogito.jackson.utils.ObjectMapperFactory; +import org.kie.kogito.serverless.workflow.executor.StaticWorkflowApplication; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.serverlessworkflow.api.Workflow; + +import static org.kie.kogito.serverless.workflow.fluent.ActionBuilder.call; +import static org.kie.kogito.serverless.workflow.fluent.FunctionBuilder.expr; +import static org.kie.kogito.serverless.workflow.fluent.StateBuilder.operation; +import static org.kie.kogito.serverless.workflow.fluent.WorkflowBuilder.workflow; + +public class JQInterpolation { + + private static final Logger logger = LoggerFactory.getLogger(JQInterpolation.class); + + public static void main(String[] args) throws IOException { + try (StaticWorkflowApplication application = StaticWorkflowApplication.create()) { + logger.info(application.execute(getWorkflow(), ObjectMapperFactory.get().createObjectNode().put("name", "Javierito").put("language", "Spanish")).getWorkflowdata().toPrettyString()); + + } + } + + static Workflow getWorkflow() { + final String INTERPOLATION = "interpolation"; + return workflow("PlayingWithExpression").function(expr(INTERPOLATION, "{greeting: \"My name is \\(.name). My language is \\(.language)\"}")) + .start(operation().action(call(INTERPOLATION))).end().build(); + } +} diff --git a/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ParallelRest.java b/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ParallelRest.java new file mode 100644 index 0000000000..58a5d3871c --- /dev/null +++ b/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ParallelRest.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.serverless.workflow.examples; + +import java.util.Map; + +import org.kie.kogito.process.Process; +import org.kie.kogito.serverless.workflow.actions.WorkflowLogLevel; +import org.kie.kogito.serverless.workflow.executor.StaticWorkflowApplication; +import org.kie.kogito.serverless.workflow.fluent.FunctionBuilder.HttpMethod; +import org.kie.kogito.serverless.workflow.models.JsonNodeModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +import io.serverlessworkflow.api.Workflow; + +import static org.kie.kogito.serverless.workflow.fluent.ActionBuilder.call; +import static org.kie.kogito.serverless.workflow.fluent.ActionBuilder.log; +import static org.kie.kogito.serverless.workflow.fluent.ActionBuilder.subprocess; +import static org.kie.kogito.serverless.workflow.fluent.FunctionBuilder.rest; +import static org.kie.kogito.serverless.workflow.fluent.StateBuilder.operation; +import static org.kie.kogito.serverless.workflow.fluent.StateBuilder.parallel; +import static org.kie.kogito.serverless.workflow.fluent.WorkflowBuilder.jsonObject; +import static org.kie.kogito.serverless.workflow.fluent.WorkflowBuilder.workflow; + +public class ParallelRest { + + private static final Logger logger = LoggerFactory.getLogger(ParallelRest.class); + + public static void main(String[] args) { + try (StaticWorkflowApplication application = StaticWorkflowApplication.create()) { + // create a reusable process for several executions + Process process = application.process(getWorkflow(application)); + // execute it with one person name + logger.info(application.execute(process, Map.of("name", "Javier")).getWorkflowdata().toPrettyString()); + // execute it with another person name + logger.info(application.execute(process, Map.of("name", "Alba")).getWorkflowdata().toPrettyString()); + } + } + + static Workflow getWorkflow(StaticWorkflowApplication application) { + ObjectNode nameArgs = jsonObject().put("name", ".name"); + + // Define a subflow process that retrieve country information from the given name + Workflow subflow = workflow("GetCountry") + // subflow consist of just one state with two sequential actions + .start(operation() + // call rest function to retrieve country id + .action(call(rest("getCountryId", HttpMethod.get, "https://api.nationalize.io:/?name={name}"), nameArgs) + // extract relevant information from the response using JQ expression + .resultFilter(".country[0].country_id").outputFilter(".id")) + // call rest function to retrieve country information from country id + .action(call(rest("getCountryInfo", HttpMethod.get, "https://restcountries.com/v3.1/alpha/{id}"), jsonObject().put("id", ".id")) + // we are only interested in country name, longitude and latitude + .resultFilter("{country: {name:.[].name.common, latitude: .[].latlng[0], longitude: .[].latlng[1] }}")) + // return only country field to parent flow + .outputFilter("{country}")) + .end().build(); + + Process subprocess = application.process(subflow); + // This is the main flow, it invokes two services (one for retrieving the age and another to get the gender of the given name )and one subprocess (the country one defined above) in parallel + // Once the three of them has been executed, if age is greater than 50, it retrieve the weather information for the retrieved country, + // Else, it gets the list of universities for that country. + return workflow("FullExample") + // Api key to be used in getting weather call + .constant("apiKey", "2482c1d33308a7cffedff5764e9ef203") + // Starts performing retrieval of gender, country and age from the given name on parallel + .start(parallel() + .newBranch().action(call(rest("getAge", HttpMethod.get, "https://api.agify.io/?name={name}"), nameArgs).resultFilter("{age}")).endBranch() + .newBranch().action(subprocess(subprocess)).endBranch() + .newBranch().action(call(rest("getGender", HttpMethod.get, "https://api.genderize.io/?name={name}"), nameArgs).resultFilter("{gender}")).endBranch()) + // once done, logs the age (using Jq string interpolation) + .next(operation().action(log(WorkflowLogLevel.INFO, "\"Age is \\(.age)\""))) + // If age is less that fifty, retrieve the list of universities (the parameters object is built using jq expressions) + .when(".age<50").next(operation().action(call(rest("getUniversities", HttpMethod.get, "http://universities.hipolabs.com/search?country={country}"), + jsonObject().put("country", ".country.name")).resultFilter(".[].name").outputFilter(".universities"))) + // Else retrieve the weather for that country capital latitude and longitude (note how parameters are build from model info) + .end().or() + .next(operation().action(call(rest("getWeather", HttpMethod.get, "https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={appid}"), + jsonObject().put("lat", ".country.latitude").put("lon", ".country.longitude").put("appid", "$CONST.apiKey")) + .resultFilter("{weather:.main}"))) + .end().build(); + } +} diff --git a/serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/division.sw.json b/serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/division.sw.json new file mode 100644 index 0000000000..e6be4fecad --- /dev/null +++ b/serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/division.sw.json @@ -0,0 +1,26 @@ +{ + "id": "division", + "version": "1.0", + "name": "Workflow Expression example", + "start": "divide", + "functions": [ + { + "name": "divide", + "type": "expression", + "operation": ".number1 / .number2" + } + ], + "states": [ + { + "name": "divide", + "type": "operation", + "actions": [ + { + "name": "divide", + "functionRef": "divide" + } + ], + "end": true + } + ] +} diff --git a/serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/expression.sw.json b/serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/expression.sw.json new file mode 100644 index 0000000000..dcf80ae8ce --- /dev/null +++ b/serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/expression.sw.json @@ -0,0 +1,73 @@ +{ + "id": "expression", + "version": "1.0", + "name": "Workflow Expression example", + "constants" : { + "Dog" : { + "castellano" : "perro", + "leones": "perru", + "gallego" : "can", + "aragones" : "cocho", + "catalan" : "gos", + "vasco": "txakurra" + } + }, + "dataInputSchema" : "schema/expression.json", + "description": "An example of how to use a JQ expression assignment", + "start": "squareState", + "extensions" : [ { + "extensionid": "workflow-output-schema", + "outputSchema": "schema/result.json" + } + ], + "functions": [ + { + "name": "max", + "type": "expression", + "operation": "{max: .numbers | max_by(.x), min: .numbers | min_by(.y)}" + }, + { + "name": "printMessage", + "type": "custom", + "operation": "sysout" + } + ], + "states": [ + { + "name": "squareState", + "type": "operation", + "actions": [ + { + "name": "maxAction", + "functionRef": { + "refName": "max" + }, + "actionDataFilter": { + "results" : ".max.x", + "toStateData" : ".number" + } + } + ], + "transition": "finish" + }, + { + "name": "finish", + "type": "operation", + "stateDataFilter": { + "input": "{result: .number}" + }, + "actions": [ + { + "name": "printAction", + "functionRef": { + "refName": "printMessage", + "arguments": { + "message": ".result" + } + } + } + ], + "end": true + } + ] +} diff --git a/serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/message.txt b/serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/message.txt new file mode 100644 index 0000000000..01b797e5bc --- /dev/null +++ b/serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/message.txt @@ -0,0 +1 @@ + , congratulations, you are a happy user of serverless workflow \ No newline at end of file diff --git a/serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/schema/complex.json b/serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/schema/complex.json new file mode 100644 index 0000000000..7a4e0728f6 --- /dev/null +++ b/serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/schema/complex.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Complex", + "description": "Complex number", + "type": "object", + "properties": { + "x" : {"type":"number"}, + "y" : {"type":"number"} + + }, + "required": ["x","y"] +} \ No newline at end of file diff --git a/serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/schema/expression.json b/serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/schema/expression.json new file mode 100644 index 0000000000..3633e4e547 --- /dev/null +++ b/serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/schema/expression.json @@ -0,0 +1,15 @@ +{ + "$id": "classpath:/schema/expression.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Expression", + "description": "Schema for expression test", + "type": "object", + "properties": { + "numbers": { + "description": "The array of numbers to be operated with", + "type": "array", + "items": {"$ref" : "complex.json"} + } + }, + "required": ["numbers"] +} \ No newline at end of file diff --git a/serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/schema/result.json b/serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/schema/result.json new file mode 100644 index 0000000000..c5a55d1d5e --- /dev/null +++ b/serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/schema/result.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Expression Output", + "description": "Output schema for expression example", + "type": "object", + "properties": { + "result": { + "type" : "number", + "description": "Maximum value on x" + } + }, + "required": ["result"] +} \ No newline at end of file diff --git a/serverless-workflow-examples/sonata-workflow-fluent/src/test/java/org/kie/kogito/serverless/workflow/examples/WorkflowFluentExamplesTest.java b/serverless-workflow-examples/sonata-workflow-fluent/src/test/java/org/kie/kogito/serverless/workflow/examples/WorkflowFluentExamplesTest.java new file mode 100644 index 0000000000..76e2ae5ffd --- /dev/null +++ b/serverless-workflow-examples/sonata-workflow-fluent/src/test/java/org/kie/kogito/serverless/workflow/examples/WorkflowFluentExamplesTest.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.serverless.workflow.examples; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.kie.kogito.serverless.workflow.executor.StaticWorkflowApplication; +import org.kie.kogito.serverless.workflow.utils.ServerlessWorkflowUtils; +import org.kie.kogito.serverless.workflow.utils.WorkflowFormat; + +import com.fasterxml.jackson.databind.node.IntNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; + +import io.serverlessworkflow.api.Workflow; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.kie.kogito.serverless.workflow.fluent.WorkflowBuilder.jsonObject; + +public class WorkflowFluentExamplesTest { + + private static StaticWorkflowApplication application; + + @BeforeAll + static void init() { + application = StaticWorkflowApplication.create(); + } + + @AfterAll + static void cleanUp() { + application.close(); + } + + @Test + void testHelloWorldDefinition() { + ObjectNode expected = jsonObject().put("greeting", "Hello World").put("mantra", "Serverless Workflow is awesome!"); + assertThat(application.execute(HelloWorld.getWorkflow(), Collections.emptyMap()).getWorkflowdata()).isEqualTo(expected); + } + + @Test + void testForEachJavaDefinition() { + assertThat(application.execute(ForEachJavaExample.getWorkflow(), Map.of("names", Arrays.asList("Javi", "Mark"), "fileName", "message.txt")).getWorkflowdata().get("messages")) + .containsExactly(new TextNode("Javi , congratulations, you are a happy user of serverless workflow"), + new TextNode("Mark , congratulations, you are a happy user of serverless workflow")); + } + + @Test + void testJQInterpolation() { + assertThat(application.execute(JQInterpolation.getWorkflow(), Map.of("name", "Javierito", "language", "Spanish")).getWorkflowdata().get("greeting")) + .isEqualTo(new TextNode("My name is Javierito. My language is Spanish")); + } + + @Test + void testConcatenationDefinition() { + assertThat(application.execute(ConcatenationExample.getWorkflow(), Map.of("name", "Javier", "surname", "Tirado")).getWorkflowdata()) + .isEqualTo(new TextNode("My name is Javier and my surname is Tirado")); + } + + @Test + void testDivissionDefinition() throws IOException { + assertThat(application.execute( + getWorkflow("division.sw.json"), Map.of("number1", 4, "number2", 2)).getWorkflowdata().get("response")).isEqualTo(new IntNode(2)); + } + + @Test + void testExpressionDefinition() throws IOException { + assertThat(application.execute( + getWorkflow("expression.sw.json"), Map.of("numbers", Arrays.asList(Map.of("x", 3, "y", 4), Map.of("x", 5, "y", 7)))).getWorkflowdata().get("result")).isEqualTo(new IntNode(5)); + } + + private Workflow getWorkflow(String filename) throws IOException { + try (Reader in = new InputStreamReader(ClassLoader.getSystemResourceAsStream(filename))) { + return ServerlessWorkflowUtils.getWorkflow(in, WorkflowFormat.JSON); + } + } +} From 15878d74c54e7b74b71c7e519b31dd4e22c5d987 Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti Date: Fri, 15 Mar 2024 17:27:15 +0100 Subject: [PATCH 2/5] [KOGITO-9454] nice pom --- .../sonata-workflow-fluent/README.md | 16 ++++++++++++++++ .../sonata-workflow-fluent/pom.xml | 5 ++--- ...catenationExample.java => Concatenation.java} | 4 ++-- ...{ForEachJavaExample.java => ForEachJava.java} | 6 +++--- .../examples/WorkflowFluentExamplesTest.java | 4 ++-- 5 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 serverless-workflow-examples/sonata-workflow-fluent/README.md rename serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/{ConcatenationExample.java => Concatenation.java} (97%) rename serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/{ForEachJavaExample.java => ForEachJava.java} (97%) diff --git a/serverless-workflow-examples/sonata-workflow-fluent/README.md b/serverless-workflow-examples/sonata-workflow-fluent/README.md new file mode 100644 index 0000000000..cdd38688dd --- /dev/null +++ b/serverless-workflow-examples/sonata-workflow-fluent/README.md @@ -0,0 +1,16 @@ +## Sonata Workflow Embedded examples + +Contains several examples of serverless workflow embedded execution, located at package `org.kie.kogito.serverless.workflow.examples`. + +Each example consist of a commented Java class that can be run using its main method. + +The recommend order of execution is + +* HelloWorld +* Concatenation +* JQInterpolation +* ForEachJava +* ParallelRest + +Read comments, run and enjoy! + \ No newline at end of file diff --git a/serverless-workflow-examples/sonata-workflow-fluent/pom.xml b/serverless-workflow-examples/sonata-workflow-fluent/pom.xml index 5fc537c5c0..122a880cd3 100644 --- a/serverless-workflow-examples/sonata-workflow-fluent/pom.xml +++ b/serverless-workflow-examples/sonata-workflow-fluent/pom.xml @@ -7,13 +7,12 @@ sonata-workflow-fluent Kogito Example :: Sonata Workflow:: Java Embedded examples - 2.0.0-SNAPSHOT org.kie.kogito kogito-bom 999-SNAPSHOT 17 - ${version.surefire.plugin} + SonataWorkflowFluent @@ -47,4 +46,4 @@ test - \ No newline at end of file + diff --git a/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ConcatenationExample.java b/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/Concatenation.java similarity index 97% rename from serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ConcatenationExample.java rename to serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/Concatenation.java index 477a781a5f..a10a515c23 100644 --- a/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ConcatenationExample.java +++ b/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/Concatenation.java @@ -35,9 +35,9 @@ import static org.kie.kogito.serverless.workflow.fluent.StateBuilder.operation; import static org.kie.kogito.serverless.workflow.fluent.WorkflowBuilder.workflow; -public class ConcatenationExample { +public class Concatenation { - private static final Logger logger = LoggerFactory.getLogger(ConcatenationExample.class); + private static final Logger logger = LoggerFactory.getLogger(Concatenation.class); public static void main(String[] args) { try (StaticWorkflowApplication application = StaticWorkflowApplication.create()) { diff --git a/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ForEachJavaExample.java b/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ForEachJava.java similarity index 97% rename from serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ForEachJavaExample.java rename to serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ForEachJava.java index 8b9792a98c..52c74e5cfc 100644 --- a/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ForEachJavaExample.java +++ b/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ForEachJava.java @@ -37,9 +37,9 @@ import static org.kie.kogito.serverless.workflow.fluent.StateBuilder.operation; import static org.kie.kogito.serverless.workflow.fluent.WorkflowBuilder.workflow; -public class ForEachJavaExample { +public class ForEachJava { - private static final Logger logger = LoggerFactory.getLogger(ForEachJavaExample.class); + private static final Logger logger = LoggerFactory.getLogger(ForEachJava.class); public static void main(String[] args) { try (StaticWorkflowApplication application = StaticWorkflowApplication.create()) { @@ -53,7 +53,7 @@ static Workflow getWorkflow() { // The flow accepts a list of names and suffix them with a message read from a file return workflow("ForEachExample") // first load the message from the file and store it in message property - .start(operation().action(call(java("getMessage", ForEachJavaExample::addAdvice), ".fileName"))) + .start(operation().action(call(java("getMessage", ForEachJava::addAdvice), ".fileName"))) // then for each element in input names concatenate it with that message .next(forEach(".names").loopVar("name").outputCollection(".messages") // jq expression that suffix each name with the message retrieved from the file diff --git a/serverless-workflow-examples/sonata-workflow-fluent/src/test/java/org/kie/kogito/serverless/workflow/examples/WorkflowFluentExamplesTest.java b/serverless-workflow-examples/sonata-workflow-fluent/src/test/java/org/kie/kogito/serverless/workflow/examples/WorkflowFluentExamplesTest.java index 76e2ae5ffd..070ef61e15 100644 --- a/serverless-workflow-examples/sonata-workflow-fluent/src/test/java/org/kie/kogito/serverless/workflow/examples/WorkflowFluentExamplesTest.java +++ b/serverless-workflow-examples/sonata-workflow-fluent/src/test/java/org/kie/kogito/serverless/workflow/examples/WorkflowFluentExamplesTest.java @@ -63,7 +63,7 @@ void testHelloWorldDefinition() { @Test void testForEachJavaDefinition() { - assertThat(application.execute(ForEachJavaExample.getWorkflow(), Map.of("names", Arrays.asList("Javi", "Mark"), "fileName", "message.txt")).getWorkflowdata().get("messages")) + assertThat(application.execute(ForEachJava.getWorkflow(), Map.of("names", Arrays.asList("Javi", "Mark"), "fileName", "message.txt")).getWorkflowdata().get("messages")) .containsExactly(new TextNode("Javi , congratulations, you are a happy user of serverless workflow"), new TextNode("Mark , congratulations, you are a happy user of serverless workflow")); } @@ -76,7 +76,7 @@ void testJQInterpolation() { @Test void testConcatenationDefinition() { - assertThat(application.execute(ConcatenationExample.getWorkflow(), Map.of("name", "Javier", "surname", "Tirado")).getWorkflowdata()) + assertThat(application.execute(Concatenation.getWorkflow(), Map.of("name", "Javier", "surname", "Tirado")).getWorkflowdata()) .isEqualTo(new TextNode("My name is Javier and my surname is Tirado")); } From ac47e8370e72b36504a23082277d98d01e57b0cb Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti Date: Mon, 18 Mar 2024 12:03:36 +0100 Subject: [PATCH 3/5] [KOGITO-9454] Improve readability of division --- .../src/main/resources/division.sw.json | 4 ++-- .../workflow/examples/WorkflowFluentExamplesTest.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/division.sw.json b/serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/division.sw.json index e6be4fecad..73b9960357 100644 --- a/serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/division.sw.json +++ b/serverless-workflow-examples/sonata-workflow-fluent/src/main/resources/division.sw.json @@ -1,13 +1,13 @@ { "id": "division", "version": "1.0", - "name": "Workflow Expression example", + "name": "division", "start": "divide", "functions": [ { "name": "divide", "type": "expression", - "operation": ".number1 / .number2" + "operation": ".numerator / .denominator" } ], "states": [ diff --git a/serverless-workflow-examples/sonata-workflow-fluent/src/test/java/org/kie/kogito/serverless/workflow/examples/WorkflowFluentExamplesTest.java b/serverless-workflow-examples/sonata-workflow-fluent/src/test/java/org/kie/kogito/serverless/workflow/examples/WorkflowFluentExamplesTest.java index 070ef61e15..11fe8b4739 100644 --- a/serverless-workflow-examples/sonata-workflow-fluent/src/test/java/org/kie/kogito/serverless/workflow/examples/WorkflowFluentExamplesTest.java +++ b/serverless-workflow-examples/sonata-workflow-fluent/src/test/java/org/kie/kogito/serverless/workflow/examples/WorkflowFluentExamplesTest.java @@ -81,9 +81,9 @@ void testConcatenationDefinition() { } @Test - void testDivissionDefinition() throws IOException { + void testDivisionDefinition() throws IOException { assertThat(application.execute( - getWorkflow("division.sw.json"), Map.of("number1", 4, "number2", 2)).getWorkflowdata().get("response")).isEqualTo(new IntNode(2)); + getWorkflow("division.sw.json"), Map.of("numerator", 4, "denominator", 2)).getWorkflowdata().get("response")).isEqualTo(new IntNode(2)); } @Test From 2d33a3dc13c5095165cdcc99687267b9ca7fc1aa Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti <65240126+fjtirado@users.noreply.github.com> Date: Mon, 18 Mar 2024 16:29:38 +0100 Subject: [PATCH 4/5] Update serverless-workflow-examples/sonata-workflow-fluent/pom.xml Co-authored-by: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com> --- serverless-workflow-examples/sonata-workflow-fluent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serverless-workflow-examples/sonata-workflow-fluent/pom.xml b/serverless-workflow-examples/sonata-workflow-fluent/pom.xml index 122a880cd3..75423b4bec 100644 --- a/serverless-workflow-examples/sonata-workflow-fluent/pom.xml +++ b/serverless-workflow-examples/sonata-workflow-fluent/pom.xml @@ -6,7 +6,7 @@ 999-SNAPSHOT sonata-workflow-fluent - Kogito Example :: Sonata Workflow:: Java Embedded examples + Kogito Example :: SonataFlow :: Java Embedded examples org.kie.kogito kogito-bom From 1689566c2a2a25a34b60012c0c39abba72110828 Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti <65240126+fjtirado@users.noreply.github.com> Date: Mon, 18 Mar 2024 16:29:51 +0100 Subject: [PATCH 5/5] Update serverless-workflow-examples/sonata-workflow-fluent/pom.xml Co-authored-by: Ricardo Zanini <1538000+ricardozanini@users.noreply.github.com> --- serverless-workflow-examples/sonata-workflow-fluent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/serverless-workflow-examples/sonata-workflow-fluent/pom.xml b/serverless-workflow-examples/sonata-workflow-fluent/pom.xml index 75423b4bec..2f46974af5 100644 --- a/serverless-workflow-examples/sonata-workflow-fluent/pom.xml +++ b/serverless-workflow-examples/sonata-workflow-fluent/pom.xml @@ -12,7 +12,7 @@ kogito-bom 999-SNAPSHOT 17 - SonataWorkflowFluent + SonataFlowFluent