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/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 new file mode 100644 index 0000000000..2f46974af5 --- /dev/null +++ b/serverless-workflow-examples/sonata-workflow-fluent/pom.xml @@ -0,0 +1,49 @@ + + 4.0.0 + + org.kie.kogito.examples + serverless-workflow-examples + 999-SNAPSHOT + + sonata-workflow-fluent + Kogito Example :: SonataFlow :: Java Embedded examples + + org.kie.kogito + kogito-bom + 999-SNAPSHOT + 17 + SonataFlowFluent + + + + + ${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 + + + diff --git a/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/Concatenation.java b/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/Concatenation.java new file mode 100644 index 0000000000..a10a515c23 --- /dev/null +++ b/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/Concatenation.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 Concatenation { + + private static final Logger logger = LoggerFactory.getLogger(Concatenation.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/ForEachJava.java b/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ForEachJava.java new file mode 100644 index 0000000000..52c74e5cfc --- /dev/null +++ b/serverless-workflow-examples/sonata-workflow-fluent/src/main/java/org/kie/kogito/serverless/workflow/examples/ForEachJava.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 ForEachJava { + + private static final Logger logger = LoggerFactory.getLogger(ForEachJava.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", 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 + .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..73b9960357 --- /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": "division", + "start": "divide", + "functions": [ + { + "name": "divide", + "type": "expression", + "operation": ".numerator / .denominator" + } + ], + "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..11fe8b4739 --- /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(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")); + } + + @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(Concatenation.getWorkflow(), Map.of("name", "Javier", "surname", "Tirado")).getWorkflowdata()) + .isEqualTo(new TextNode("My name is Javier and my surname is Tirado")); + } + + @Test + void testDivisionDefinition() throws IOException { + assertThat(application.execute( + getWorkflow("division.sw.json"), Map.of("numerator", 4, "denominator", 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); + } + } +}