diff --git a/experimental/lambda/pom.xml b/experimental/lambda/pom.xml new file mode 100644 index 00000000..72e4c0b2 --- /dev/null +++ b/experimental/lambda/pom.xml @@ -0,0 +1,45 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-experimental + 8.0.0-SNAPSHOT + + serverlessworkflow-experimental-lambda + ServelessWorkflow:: Experimental:: lambda + + + io.serverlessworkflow + serverlessworkflow-experimental-types + + + io.serverlessworkflow + serverlessworkflow-impl-core + + + 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/lambda/src/main/java/io/serverlessworkflow/impl/executors/JavaCallExecutor.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/JavaCallExecutor.java new file mode 100644 index 00000000..8d166986 --- /dev/null +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/JavaCallExecutor.java @@ -0,0 +1,71 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.CallJava; +import io.serverlessworkflow.api.types.TaskBase; +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.resources.ResourceLoader; +import java.util.concurrent.CompletableFuture; + +public class JavaCallExecutor implements CallableTask { + + @Override + public void init(CallJava task, WorkflowApplication application, ResourceLoader loader) {} + + @Override + public CompletableFuture apply( + WorkflowContext workflowContext, TaskContext taskContext, WorkflowModel input) { + WorkflowModelFactory modelFactory = workflowContext.definition().application().modelFactory(); + if (taskContext.task() instanceof CallJava.CallJavaFunction function) { + return CompletableFuture.completedFuture( + modelFactory.fromAny(function.function().apply(input.asJavaObject()))); + } else if (taskContext.task() instanceof CallJava.CallJavaLoopFunction function) { + return CompletableFuture.completedFuture( + modelFactory.fromAny( + function + .function() + .apply( + input.asJavaObject(), + safeObject(taskContext.variables().get(function.varName()))))); + } else if (taskContext.task() instanceof CallJava.CallJavaLoopFunctionIndex function) { + return CompletableFuture.completedFuture( + modelFactory.fromAny( + function + .function() + .apply( + input.asJavaObject(), + safeObject(taskContext.variables().get(function.varName())), + (Integer) safeObject(taskContext.variables().get(function.indexName()))))); + } else if (taskContext.task() instanceof CallJava.CallJavaConsumer consumer) { + consumer.consumer().accept(input.asJavaObject()); + } + return CompletableFuture.completedFuture(input); + } + + @Override + public boolean accept(Class clazz) { + return CallJava.class.isAssignableFrom(clazz); + } + + static Object safeObject(Object obj) { + return obj instanceof WorkflowModel model ? model.asJavaObject() : obj; + } +} diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/JavaForExecutorBuilder.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/JavaForExecutorBuilder.java new file mode 100644 index 00000000..faa1942c --- /dev/null +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/JavaForExecutorBuilder.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 static io.serverlessworkflow.impl.executors.JavaCallExecutor.safeObject; + +import io.serverlessworkflow.api.types.ForTask; +import io.serverlessworkflow.api.types.ForTaskFunction; +import io.serverlessworkflow.api.types.Workflow; +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); + if (task instanceof ForTaskFunction taskFunctions) {} + } + + protected Optional buildWhileFilter() { + if (task instanceof ForTaskFunction taskFunctions) { + LoopPredicateIndex whilePred = taskFunctions.getWhilePredicate(); + 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( + item == null + || whilePred.test( + n.asJavaObject(), + item, + (Integer) safeObject(t.variables().get(indexName)))); + }); + } + } + return super.buildWhileFilter(); + } + + protected WorkflowFilter buildCollectionFilter() { + return task instanceof ForTaskFunction taskFunctions + ? WorkflowUtils.buildWorkflowFilter(application, null, taskFunctions.getCollection()) + : super.buildCollectionFilter(); + } +} diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/JavaSwitchExecutorBuilder.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/JavaSwitchExecutorBuilder.java new file mode 100644 index 00000000..3b42825d --- /dev/null +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/JavaSwitchExecutorBuilder.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.impl.executors; + +import io.serverlessworkflow.api.types.SwitchCase; +import io.serverlessworkflow.api.types.SwitchCaseFunction; +import io.serverlessworkflow.api.types.SwitchTask; +import io.serverlessworkflow.api.types.Workflow; +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; + +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, function.predicate())) + : super.buildFilter(switchCase); + } +} diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/JavaTaskExecutorFactory.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/JavaTaskExecutorFactory.java new file mode 100644 index 00000000..26177287 --- /dev/null +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/executors/JavaTaskExecutorFactory.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.executors; + +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.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/JavaExpressionFactory.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/JavaExpressionFactory.java new file mode 100644 index 00000000..a6e89ae8 --- /dev/null +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/JavaExpressionFactory.java @@ -0,0 +1,66 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.WorkflowFilter; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowModelFactory; +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 Predicate pred) { + return (w, t, n) -> modelFactory.from(pred.test(n.asJavaObject())); + } 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); + } + } + + @Override + public WorkflowModelFactory modelFactory() { + return modelFactory; + } +} diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/JavaModel.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/JavaModel.java new file mode 100644 index 00000000..0e4b4df1 --- /dev/null +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/JavaModel.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; + +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.Optional; +import java.util.function.BiConsumer; + +public class JavaModel implements WorkflowModel { + + private Object object; + + static final JavaModel TrueModel = new JavaModel(Boolean.TRUE); + static final JavaModel FalseModel = new JavaModel(Boolean.FALSE); + static final JavaModel NullModel = new JavaModel(null); + + JavaModel(Object object) { + this.object = 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; + } + + @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 && object.getClass().isAssignableFrom(clazz) + ? Optional.of(clazz.cast(object)) + : Optional.empty(); + } +} diff --git a/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/JavaModelCollection.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/JavaModelCollection.java new file mode 100644 index 00000000..065d8832 --- /dev/null +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/JavaModelCollection.java @@ -0,0 +1,147 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.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 { + + private final Collection object; + + JavaModelCollection() { + this.object = new ArrayList<>(); + } + + JavaModelCollection(Collection object) { + this.object = 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 : 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.asIs()); + } + + @Override + public boolean remove(Object o) { + return object.remove(((WorkflowModel) o).asIs()); + } + + @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/JavaModelFactory.java b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/JavaModelFactory.java new file mode 100644 index 00000000..6034d33a --- /dev/null +++ b/experimental/lambda/src/main/java/io/serverlessworkflow/impl/expressions/JavaModelFactory.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.expressions; + +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; + +public class JavaModelFactory implements WorkflowModelFactory { + + @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 ? JavaModel.TrueModel : JavaModel.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 JavaModel.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..e413059c --- /dev/null +++ b/experimental/lambda/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.CallableTask @@ -0,0 +1 @@ +io.serverlessworkflow.impl.executors.JavaCallExecutor \ 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..6fd5dc15 --- /dev/null +++ b/experimental/lambda/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.TaskExecutorFactory @@ -0,0 +1 @@ +io.serverlessworkflow.impl.executors.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..171ce036 --- /dev/null +++ b/experimental/lambda/src/main/resources/META-INF/services/io.serverlessworkflow.impl.expressions.ExpressionFactory @@ -0,0 +1 @@ +io.serverlessworkflow.impl.expressions.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..9118ec06 --- /dev/null +++ b/experimental/lambda/src/test/java/io/serverless/workflow/impl/CallTest.java @@ -0,0 +1,158 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR 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.CallJava; +import io.serverlessworkflow.api.types.CallTaskJava; +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.ForTaskFunction; +import io.serverlessworkflow.api.types.SwitchCaseFunction; +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.Workflow; +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; + +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)))))); + + 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(this::isEven) + .withCollection(v -> (Collection) v) + .withFor(forConfig) + .withDo( + List.of( + new TaskItem( + "javaCall", + new Task() + .withCallTask( + new CallTaskJava( + CallJava.loopFunction( + this::sum, + forConfig.getEach())))))))))); + + 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 = + 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(this::isOdd) + .withThen( + new FlowDirective() + .withFlowDirectiveEnum( + FlowDirectiveEnum.END))))))), + new TaskItem( + "java", + new Task() + .withCallTask(new CallTaskJava(CallJava.function(this::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); + } + } + + private boolean isEven(Object model, Integer number) { + return !isOdd(number); + } + + private boolean isOdd(Integer number) { + return number % 2 != 0; + } + + private int zero(Integer value) { + return 0; + } + + private 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/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..7702fffe --- /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.OutputAsFunction; +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.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/experimental/lambda/src/test/java/io/serverless/workflow/impl/Person.java b/experimental/lambda/src/test/java/io/serverless/workflow/impl/Person.java new file mode 100644 index 00000000..9594c285 --- /dev/null +++ b/experimental/lambda/src/test/java/io/serverless/workflow/impl/Person.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.serverless.workflow.impl; + +record Person(String name, int age) {} diff --git a/experimental/pom.xml b/experimental/pom.xml new file mode 100644 index 00000000..409e68c2 --- /dev/null +++ b/experimental/pom.xml @@ -0,0 +1,39 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-parent + 8.0.0-SNAPSHOT + + serverlessworkflow-experimental + pom + ServerlessWorkflow:: 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} + + + + + types + lambda + + \ No newline at end of file diff --git a/experimental/types/pom.xml b/experimental/types/pom.xml new file mode 100644 index 00000000..dea3931d --- /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 + ServelessWorkflow:: Experimental:: Types + + + io.serverlessworkflow + serverlessworkflow-types + + + \ No newline at end of file diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/CallJava.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/CallJava.java new file mode 100644 index 00000000..c3115de2 --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/CallJava.java @@ -0,0 +1,118 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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; + +import io.serverlessworkflow.impl.expressions.LoopFunction; +import io.serverlessworkflow.impl.expressions.LoopFunctionIndex; +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 CallJava function(Function function) { + return new CallJavaFunction<>(function); + } + + 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; + + public CallJavaFunction(Function function) { + this.function = function; + } + + public Function function() { + return function; + } + } + + 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/CallTaskJava.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/CallTaskJava.java new file mode 100644 index 00000000..e1b406ec --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/CallTaskJava.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; + +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/ExportAsFunction.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/ExportAsFunction.java new file mode 100644 index 00000000..fd279cd2 --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/ExportAsFunction.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.api.types; + +import java.util.function.Function; + +public class ExportAsFunction extends ExportAs { + + public ExportAs withFunction(Function value) { + setObject(value); + return this; + } +} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/ForTaskFunction.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/ForTaskFunction.java new file mode 100644 index 00000000..00e29614 --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/ForTaskFunction.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.api.types; + +import io.serverlessworkflow.impl.expressions.LoopPredicate; +import io.serverlessworkflow.impl.expressions.LoopPredicateIndex; +import java.util.Collection; +import java.util.function.Function; + +public class ForTaskFunction extends ForTask { + + private static final long serialVersionUID = 1L; + private LoopPredicateIndex whilePredicate; + private Function> collection; + + public ForTaskFunction withWhile(LoopPredicate whilePredicate) { + this.whilePredicate = toPredicateIndex(whilePredicate); + return this; + } + + private LoopPredicateIndex toPredicateIndex(LoopPredicate whilePredicate) { + return (model, item, index) -> whilePredicate.test(model, item); + } + + public ForTaskFunction withWhile(LoopPredicateIndex whilePredicate) { + this.whilePredicate = whilePredicate; + return this; + } + + public ForTaskFunction withCollection(Function> collection) { + this.collection = collection; + return this; + } + + public LoopPredicateIndex getWhilePredicate() { + return whilePredicate; + } + + public Function> getCollection() { + return collection; + } +} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/InputFromFunction.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/InputFromFunction.java new file mode 100644 index 00000000..abea6daa --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/InputFromFunction.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.api.types; + +import java.util.function.Function; + +public class InputFromFunction extends InputFrom { + + public InputFrom withFunction(Function value) { + setObject(value); + return this; + } +} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/OutputAsFunction.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/OutputAsFunction.java new file mode 100644 index 00000000..7ae183a4 --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/OutputAsFunction.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.api.types; + +import java.util.function.Function; + +public class OutputAsFunction extends OutputAs { + + public OutputAs withFunction(Function value) { + setObject(value); + return this; + } +} diff --git a/experimental/types/src/main/java/io/serverlessworkflow/api/types/SwitchCaseFunction.java b/experimental/types/src/main/java/io/serverlessworkflow/api/types/SwitchCaseFunction.java new file mode 100644 index 00000000..027ab178 --- /dev/null +++ b/experimental/types/src/main/java/io/serverlessworkflow/api/types/SwitchCaseFunction.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.api.types; + +import java.util.function.Predicate; + +public class SwitchCaseFunction extends SwitchCase { + + private static final long serialVersionUID = 1L; + private Predicate predicate; + + public SwitchCaseFunction withPredicate(Predicate predicate) { + this.predicate = predicate; + return this; + } + + public Predicate predicate() { + return predicate; + } +} 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/impl/core/pom.xml b/impl/core/pom.xml index 2ea1eb9b..6cc2beed 100644 --- a/impl/core/pom.xml +++ b/impl/core/pom.xml @@ -25,30 +25,5 @@ com.github.f4b6a3 ulid-creator - - 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 - diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java index ab23f2c5..16063c60 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java @@ -125,7 +125,7 @@ public SchemaValidator getValidator(SchemaInline inline) { }; } - private TaskExecutorFactory taskFactory = DefaultTaskExecutorFactory.get(); + private TaskExecutorFactory taskFactory; private ExpressionFactory exprFactory; private Collection listeners; private ResourceLoaderFactory resourceLoaderFactory = DefaultResourceLoaderFactory.get(); @@ -211,6 +211,12 @@ public WorkflowApplication build() { .findFirst() .orElseGet(() -> EmptySchemaValidatorHolder.instance); } + if (taskFactory == null) { + taskFactory = + ServiceLoader.load(TaskExecutorFactory.class) + .findFirst() + .orElseGet(() -> DefaultTaskExecutorFactory.get()); + } return new WorkflowApplication(this); } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModelFactory.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModelFactory.java index f8cf7278..4c55723a 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModelFactory.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowModelFactory.java @@ -59,6 +59,8 @@ default WorkflowModel fromAny(Object obj) { return from(value); } else if (obj instanceof Map) { return from((Map) obj); + } else if (obj instanceof WorkflowModel model) { + return model; } else { throw new IllegalArgumentException( "Unsopported conversion for object " + obj + " of type" + obj.getClass()); 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 index 0499fced..bf63b5ed 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/DefaultTaskExecutorFactory.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/DefaultTaskExecutorFactory.java @@ -15,11 +15,6 @@ */ package io.serverlessworkflow.impl.executors; -import io.serverlessworkflow.api.types.CallAsyncAPI; -import io.serverlessworkflow.api.types.CallFunction; -import io.serverlessworkflow.api.types.CallGRPC; -import io.serverlessworkflow.api.types.CallHTTP; -import io.serverlessworkflow.api.types.CallOpenAPI; import io.serverlessworkflow.api.types.CallTask; import io.serverlessworkflow.api.types.Task; import io.serverlessworkflow.api.types.TaskBase; @@ -62,46 +57,15 @@ public TaskExecutorBuilder getTaskExecutor( ResourceLoader resourceLoader) { if (task.getCallTask() != null) { CallTask callTask = task.getCallTask(); - if (callTask.getCallHTTP() != null) { - return new CallTaskExecutorBuilder<>( + TaskBase taskBase = (TaskBase) callTask.get(); + if (taskBase != null) { + return new CallTaskExecutorBuilder( position, - callTask.getCallHTTP(), + taskBase, workflow, application, resourceLoader, - findCallTask(CallHTTP.class)); - } else if (callTask.getCallAsyncAPI() != null) { - return new CallTaskExecutorBuilder<>( - position, - callTask.getCallAsyncAPI(), - workflow, - application, - resourceLoader, - findCallTask(CallAsyncAPI.class)); - } else if (callTask.getCallGRPC() != null) { - return new CallTaskExecutorBuilder<>( - position, - callTask.getCallGRPC(), - workflow, - application, - resourceLoader, - findCallTask(CallGRPC.class)); - } else if (callTask.getCallOpenAPI() != null) { - return new CallTaskExecutorBuilder<>( - position, - callTask.getCallOpenAPI(), - workflow, - application, - resourceLoader, - findCallTask(CallOpenAPI.class)); - } else if (callTask.getCallFunction() != null) { - return new CallTaskExecutorBuilder<>( - position, - callTask.getCallFunction(), - workflow, - application, - resourceLoader, - findCallTask(CallFunction.class)); + findCallTask(taskBase.getClass())); } } else if (task.getSwitchTask() != null) { return new SwitchExecutorBuilder( 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 index 15b5e744..e0aa8d29 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForExecutor.java @@ -16,7 +16,6 @@ package io.serverlessworkflow.impl.executors; import io.serverlessworkflow.api.types.ForTask; -import io.serverlessworkflow.api.types.ForTaskConfiguration; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.TaskContext; import io.serverlessworkflow.impl.WorkflowApplication; @@ -25,7 +24,6 @@ import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowPosition; import io.serverlessworkflow.impl.WorkflowUtils; -import io.serverlessworkflow.impl.executors.RegularTaskExecutor.RegularTaskExecutorBuilder; import io.serverlessworkflow.impl.resources.ResourceLoader; import java.util.Iterator; import java.util.Optional; @@ -49,14 +47,21 @@ protected ForExecutorBuilder( WorkflowApplication application, ResourceLoader resourceLoader) { super(position, task, workflow, application, resourceLoader); - ForTaskConfiguration forConfig = task.getFor(); - this.collectionExpr = WorkflowUtils.buildWorkflowFilter(application, forConfig.getIn()); - this.whileExpr = WorkflowUtils.optionalFilter(application, task.getWhile()); + 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); 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 index d92eb1a6..1353db89 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForkExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ForkExecutor.java @@ -26,6 +26,7 @@ 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; @@ -108,6 +109,11 @@ private WorkflowModel combine(WorkflowContext context, Map .application() .modelFactory() .combine( - sortedStream.collect(Collectors.toMap(Entry::getKey, e -> e.getValue().output()))); + 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/SwitchExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/SwitchExecutor.java index 424d4c97..19a69568 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/SwitchExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/SwitchExecutor.java @@ -30,6 +30,7 @@ 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; @@ -53,15 +54,19 @@ public SwitchExecutorBuilder( super(position, task, workflow, application, resourceLoader); for (SwitchItem item : task.getSwitch()) { SwitchCase switchCase = item.getSwitchCase(); - if (switchCase.getWhen() != null) { - workflowFilters.put( - switchCase, WorkflowUtils.buildWorkflowFilter(application, switchCase.getWhen())); - } else { - defaultDirective = switchCase.getThen(); - } + 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 = diff --git a/pom.xml b/pom.xml index 732e84b0..b0a57369 100644 --- a/pom.xml +++ b/pom.xml @@ -44,6 +44,7 @@ generators serialization examples + experimental