From 806c8eb91269a94a72ed6988d06f9b821aea1971 Mon Sep 17 00:00:00 2001 From: fjtirado Date: Wed, 17 Sep 2025 14:44:56 +0200 Subject: [PATCH 1/7] [Fix #782] Adding MVStore persistence Signed-off-by: fjtirado --- .../impl/WorkflowApplication.java | 10 +- .../impl/WorkflowDefinition.java | 9 +- .../impl/WorkflowMutableInstance.java | 19 ++- impl/persistence/api/pom.xml | 16 ++ .../WorkflowIdPersistentReader.java | 31 ++++ .../WorkflowMinimumPersistenceReader.java | 30 ++++ .../WorkflowPersistenceApplication.java | 88 +++++++++++ .../WorkflowPersistenceListener.java | 63 ++++++++ .../WorkflowPersistenceWriter.java | 36 +++++ impl/persistence/bigmap/pom.xml | 16 ++ .../bigmap/BigMapIdPersistenceStore.java | 38 +++++ .../bigmap/BigMapPersistenceStore.java | 82 ++++++++++ .../bigmap/BytesBigMapPersistenceStore.java | 95 ++++++++++++ .../bigmap/DefaultBufferFactory.java | 32 ++++ .../bigmap/DefaultInputBuffer.java | 131 ++++++++++++++++ .../bigmap/DefaultOutputBuffer.java | 144 ++++++++++++++++++ .../bigmap/WorkflowBufferFactory.java | 26 ++++ .../bigmap/WorkflowInputBuffer.java | 46 ++++++ .../bigmap/WorkflowOutputBuffer.java | 46 ++++++ impl/persistence/mvstore/pom.xml | 21 +++ .../mvstore/MVStorePersistenceStore.java | 50 ++++++ impl/persistence/pom.xml | 16 ++ impl/pom.xml | 11 ++ 23 files changed, 1043 insertions(+), 13 deletions(-) create mode 100644 impl/persistence/api/pom.xml create mode 100644 impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowIdPersistentReader.java create mode 100644 impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowMinimumPersistenceReader.java create mode 100644 impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceApplication.java create mode 100644 impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceListener.java create mode 100644 impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceWriter.java create mode 100644 impl/persistence/bigmap/pom.xml create mode 100644 impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapIdPersistenceStore.java create mode 100644 impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceStore.java create mode 100644 impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceStore.java create mode 100644 impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/DefaultBufferFactory.java create mode 100644 impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/DefaultInputBuffer.java create mode 100644 impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/DefaultOutputBuffer.java create mode 100644 impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/WorkflowBufferFactory.java create mode 100644 impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/WorkflowInputBuffer.java create mode 100644 impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/WorkflowOutputBuffer.java create mode 100644 impl/persistence/mvstore/pom.xml create mode 100644 impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStorePersistenceStore.java create mode 100644 impl/persistence/pom.xml 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 8e25e651..93e89aba 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java @@ -60,7 +60,7 @@ public class WorkflowApplication implements AutoCloseable { private final Collection eventPublishers; private final boolean lifeCycleCEPublishingEnabled; - private WorkflowApplication(Builder builder) { + protected WorkflowApplication(Builder builder) { this.taskFactory = builder.taskFactory; this.exprFactory = builder.exprFactory; this.resourceLoaderFactory = builder.resourceLoaderFactory; @@ -148,7 +148,7 @@ public SchemaValidator getValidator(SchemaInline inline) { () -> new RuntimeDescriptor("reference impl", "1.0.0_alpha", Collections.emptyMap()); private boolean lifeCycleCEPublishingEnabled = true; - private Builder() {} + protected Builder() {} public Builder withListener(WorkflowExecutionListener listener) { listeners.add(listener); @@ -256,7 +256,11 @@ public Map workflowDefinitions() { public WorkflowDefinition workflowDefinition(Workflow workflow) { return definitions.computeIfAbsent( - WorkflowDefinitionId.of(workflow), k -> WorkflowDefinition.of(this, workflow)); + WorkflowDefinitionId.of(workflow), k -> createDefinition(workflow)); + } + + protected WorkflowDefinition createDefinition(Workflow workflow) { + return WorkflowDefinition.of(this, workflow); } @Override diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java index e0805c39..cc964009 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java @@ -70,7 +70,10 @@ static WorkflowDefinition of(WorkflowApplication application, Workflow workflow, } public WorkflowInstance instance(Object input) { - return new WorkflowMutableInstance(this, application.modelFactory().fromAny(input)); + WorkflowModel inputModel = application.modelFactory().fromAny(input); + inputSchemaValidator().ifPresent(v -> v.validate(inputModel)); + return new WorkflowMutableInstance( + this, application().idFactory().get(), inputModel, WorkflowStatus.PENDING); } Optional inputSchemaValidator() { @@ -108,7 +111,5 @@ public ResourceLoader resourceLoader() { } @Override - public void close() { - // TODO close resourcers hold for uncompleted process instances, if any - } + public void close() {} } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowMutableInstance.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowMutableInstance.java index c5065c36..63f98872 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowMutableInstance.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowMutableInstance.java @@ -49,18 +49,18 @@ public class WorkflowMutableInstance implements WorkflowInstance { private CompletableFuture completableFuture; private Map, TaskContext> suspended; - WorkflowMutableInstance(WorkflowDefinition definition, WorkflowModel input) { - this.id = definition.application().idFactory().get(); + public WorkflowMutableInstance( + WorkflowDefinition definition, String id, WorkflowModel input, WorkflowStatus status) { + this.id = id; this.input = input; - this.status = new AtomicReference<>(WorkflowStatus.PENDING); - definition.inputSchemaValidator().ifPresent(v -> v.validate(input)); + this.status = new AtomicReference<>(status); this.workflowContext = new WorkflowContext(definition, this); } @Override public CompletableFuture start() { - this.startedAt = Instant.now(); - this.status.set(WorkflowStatus.RUNNING); + startedAt = Instant.now(); + status.set(WorkflowStatus.RUNNING); publishEvent( workflowContext, l -> l.onWorkflowStarted(new WorkflowStartedEvent(workflowContext))); this.completableFuture = @@ -236,6 +236,13 @@ public CompletableFuture suspendedCheck(TaskContext t) { return CompletableFuture.completedFuture(t); } + // internal purposes only, not to be invoked directly by users of the API + public void restore( + WorkflowPosition position, WorkflowModel model, WorkflowModel context, Instant startedAt) { + this.startedAt = startedAt; + workflowContext.context(context); + } + @Override public boolean cancel() { try { diff --git a/impl/persistence/api/pom.xml b/impl/persistence/api/pom.xml new file mode 100644 index 00000000..79c165a4 --- /dev/null +++ b/impl/persistence/api/pom.xml @@ -0,0 +1,16 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-persistence + 8.0.0-SNAPSHOT + + serverlessworkflow-persistence-api + Serverless Workflow :: Impl :: Pesistence:: API + + + io.serverlessworkflow + serverlessworkflow-impl-core + + + \ No newline at end of file diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowIdPersistentReader.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowIdPersistentReader.java new file mode 100644 index 00000000..2067d7c2 --- /dev/null +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowIdPersistentReader.java @@ -0,0 +1,31 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence; + +import io.serverlessworkflow.impl.WorkflowDefinition; +import io.serverlessworkflow.impl.WorkflowInstance; +import java.util.Optional; + +public interface WorkflowIdPersistentReader extends WorkflowMinimumPersistenceReader { + + /** + * Allow recovering by process instance id + * + * @param workflowInstanceId + * @return + */ + Optional findById(WorkflowDefinition definition, String workflowInstanceId); +} diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowMinimumPersistenceReader.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowMinimumPersistenceReader.java new file mode 100644 index 00000000..8f52e4d6 --- /dev/null +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowMinimumPersistenceReader.java @@ -0,0 +1,30 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence; + +import io.serverlessworkflow.impl.WorkflowDefinition; +import io.serverlessworkflow.impl.WorkflowInstance; +import java.util.stream.Stream; + +public interface WorkflowMinimumPersistenceReader extends AutoCloseable { + + /** + * Allow streaming over all stored workflow instances for a certain definition + * + * @return + */ + Stream all(WorkflowDefinition definition); +} diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceApplication.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceApplication.java new file mode 100644 index 00000000..b0d048ed --- /dev/null +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceApplication.java @@ -0,0 +1,88 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence; + +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowDefinition; +import io.serverlessworkflow.impl.WorkflowInstance; + +public class WorkflowPersistenceApplication + extends WorkflowApplication { + + private final T reader; + private final WorkflowPersistenceWriter writer; + private final boolean resumeAfterReboot; + + protected WorkflowPersistenceApplication(Builder builder) { + super(builder); + this.reader = builder.reader; + this.writer = builder.writer; + this.resumeAfterReboot = builder.resumeAfterReboot; + } + + public T persitenceReader() { + return reader; + } + + public void close() { + super.close(); + try { + reader.close(); + } catch (Exception e) { + } + try { + writer.close(); + } catch (Exception e) { + } + } + + public static Builder builder( + WorkflowPersistenceWriter writer, T reader) { + return new Builder<>(writer, reader); + } + + public static class Builder + extends io.serverlessworkflow.impl.WorkflowApplication.Builder { + + private final WorkflowPersistenceWriter writer; + private final T reader; + private boolean resumeAfterReboot = true; + + protected Builder(WorkflowPersistenceWriter writer, T reader) { + this.writer = writer; + this.reader = reader; + super.withListener(new WorkflowPersistenceListener(writer)); + } + + public Builder resumeAfterReboot(boolean resumeAfterReboot) { + this.resumeAfterReboot = resumeAfterReboot; + return this; + } + + public WorkflowPersistenceApplication build() { + return new WorkflowPersistenceApplication<>(this); + } + } + + protected WorkflowDefinition createDefinition(Workflow workflow) { + WorkflowDefinition definition = super.createDefinition(workflow); + if (resumeAfterReboot) { + reader.all(definition).forEach(WorkflowInstance::resume); + } + return definition; + } +} diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceListener.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceListener.java new file mode 100644 index 00000000..0ebc47b4 --- /dev/null +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceListener.java @@ -0,0 +1,63 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence; + +import io.serverlessworkflow.impl.lifecycle.TaskStartedEvent; +import io.serverlessworkflow.impl.lifecycle.WorkflowCancelledEvent; +import io.serverlessworkflow.impl.lifecycle.WorkflowExecutionListener; +import io.serverlessworkflow.impl.lifecycle.WorkflowFailedEvent; +import io.serverlessworkflow.impl.lifecycle.WorkflowResumedEvent; +import io.serverlessworkflow.impl.lifecycle.WorkflowStartedEvent; +import io.serverlessworkflow.impl.lifecycle.WorkflowSuspendedEvent; + +public class WorkflowPersistenceListener implements WorkflowExecutionListener { + + private final WorkflowPersistenceWriter persistenceStore; + + public WorkflowPersistenceListener(WorkflowPersistenceWriter persistenceStore) { + this.persistenceStore = persistenceStore; + } + + @Override + public void onWorkflowStarted(WorkflowStartedEvent ev) { + persistenceStore.started(ev.workflowContext()); + } + + @Override + public void onWorkflowFailed(WorkflowFailedEvent ev) { + persistenceStore.failed(ev.workflowContext(), ev.cause()); + } + + @Override + public void onWorkflowCancelled(WorkflowCancelledEvent ev) { + persistenceStore.aborted(ev.workflowContext()); + } + + @Override + public void onWorkflowSuspended(WorkflowSuspendedEvent ev) { + persistenceStore.suspended(ev.workflowContext()); + } + + @Override + public void onWorkflowResumed(WorkflowResumedEvent ev) { + persistenceStore.resumed(ev.workflowContext()); + } + + @Override + public void onTaskStarted(TaskStartedEvent ev) { + persistenceStore.updated(ev.workflowContext(), ev.taskContext()); + } +} diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceWriter.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceWriter.java new file mode 100644 index 00000000..4eaaa779 --- /dev/null +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceWriter.java @@ -0,0 +1,36 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence; + +import io.serverlessworkflow.impl.TaskContextData; +import io.serverlessworkflow.impl.WorkflowContextData; + +public interface WorkflowPersistenceWriter extends AutoCloseable { + + void started(WorkflowContextData workflowContext); + + void completed(WorkflowContextData workflowContext); + + void failed(WorkflowContextData workflowContext, Throwable ex); + + void aborted(WorkflowContextData workflowContext); + + void suspended(WorkflowContextData workflowContext); + + void resumed(WorkflowContextData workflowContext); + + void updated(WorkflowContextData workflowContext, TaskContextData taskContext); +} diff --git a/impl/persistence/bigmap/pom.xml b/impl/persistence/bigmap/pom.xml new file mode 100644 index 00000000..86948c8f --- /dev/null +++ b/impl/persistence/bigmap/pom.xml @@ -0,0 +1,16 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-persistence + 8.0.0-SNAPSHOT + + serverlessworkflow-persistence-big-map + Serverless Workflow :: Impl :: Pesistence:: BigMap + + + io.serverlessworkflow + serverlessworkflow-persistence-api + + + \ No newline at end of file diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapIdPersistenceStore.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapIdPersistenceStore.java new file mode 100644 index 00000000..eb3267f5 --- /dev/null +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapIdPersistenceStore.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence.bigmap; + +import io.serverlessworkflow.impl.WorkflowContextData; +import io.serverlessworkflow.impl.WorkflowDefinition; +import io.serverlessworkflow.impl.WorkflowInstance; +import io.serverlessworkflow.impl.persistence.WorkflowIdPersistentReader; +import java.util.Optional; + +public abstract class BigMapIdPersistenceStore extends BigMapPersistenceStore + implements WorkflowIdPersistentReader { + + @Override + protected String key(WorkflowContextData workflowContext) { + return workflowContext.instanceData().id(); + } + + @Override + public Optional findById( + WorkflowDefinition definition, String workflowInstanceId) { + return Optional.ofNullable(instances(definition).get(workflowInstanceId)) + .map(v -> unmarshall(definition, v)); + } +} diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceStore.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceStore.java new file mode 100644 index 00000000..1c387907 --- /dev/null +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceStore.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.persistence.bigmap; + +import io.serverlessworkflow.impl.TaskContextData; +import io.serverlessworkflow.impl.WorkflowContextData; +import io.serverlessworkflow.impl.WorkflowDefinition; +import io.serverlessworkflow.impl.WorkflowDefinitionData; +import io.serverlessworkflow.impl.WorkflowInstance; +import io.serverlessworkflow.impl.persistence.WorkflowMinimumPersistenceReader; +import io.serverlessworkflow.impl.persistence.WorkflowPersistenceWriter; +import java.util.Map; +import java.util.stream.Stream; + +public abstract class BigMapPersistenceStore + implements WorkflowMinimumPersistenceReader, WorkflowPersistenceWriter { + + @Override + public void started(WorkflowContextData workflowContext) { + instances(workflowContext.definition()).put(key(workflowContext), marshall(workflowContext)); + } + + @Override + public void completed(WorkflowContextData workflowContext) { + instances(workflowContext.definition()).remove(workflowContext.instanceData().id()); + } + + @Override + public void failed(WorkflowContextData workflowContext, Throwable ex) { + instances(workflowContext.definition()).remove(workflowContext.instanceData().id()); + } + + @Override + public void aborted(WorkflowContextData workflowContext) { + instances(workflowContext.definition()).remove(workflowContext.instanceData().id()); + } + + @Override + public void updated(WorkflowContextData workflowContext, TaskContextData taskContext) { + instances(workflowContext.definition()).put(key(workflowContext), marshall(workflowContext)); + } + + @Override + public void suspended(WorkflowContextData workflowContext) { + // nothing to do + } + + @Override + public void resumed(WorkflowContextData workflowContext) { + // nothing to do + } + + @Override + public Stream all(WorkflowDefinition definition) { + return instances(definition).values().stream().map(value -> unmarshall(definition, value)); + } + + protected abstract WorkflowInstance unmarshall(WorkflowDefinition definition, V value); + + protected abstract K key(WorkflowContextData workflowContext); + + protected abstract Map instances(WorkflowDefinitionData definition); + + protected abstract V marshall(WorkflowContextData workflowContext, TaskContextData taskContext); + + protected V marshall(WorkflowContextData workflowContext) { + return marshall(workflowContext, null); + } +} diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceStore.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceStore.java new file mode 100644 index 00000000..a5c82579 --- /dev/null +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceStore.java @@ -0,0 +1,95 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.persistence.bigmap; + +import io.serverlessworkflow.impl.TaskContextData; +import io.serverlessworkflow.impl.WorkflowContextData; +import io.serverlessworkflow.impl.WorkflowDefinition; +import io.serverlessworkflow.impl.WorkflowInstance; +import io.serverlessworkflow.impl.WorkflowInstanceData; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowMutableInstance; +import io.serverlessworkflow.impl.WorkflowPosition; +import io.serverlessworkflow.impl.WorkflowStatus; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.time.Instant; + +public abstract class BytesBigMapPersistenceStore extends BigMapIdPersistenceStore { + + private final WorkflowBufferFactory factory; + + public BytesBigMapPersistenceStore(WorkflowBufferFactory factory) { + this.factory = factory; + } + + public BytesBigMapPersistenceStore() { + this(new DefaultBufferFactory()); + } + + @Override + protected WorkflowInstance unmarshall(WorkflowDefinition definition, byte[] bytes) { + + try (WorkflowInputBuffer reader = factory.input(new ByteArrayInputStream(bytes))) { + String id = reader.readString(); + WorkflowStatus status = reader.readEnum(WorkflowStatus.class); + WorkflowModel inputModel = readModel(bytes); + Instant startDate = reader.readInstant(); + WorkflowMutableInstance instance = + new WorkflowMutableInstance(definition, id, inputModel, status); + WorkflowModel context = readModel(bytes); + WorkflowPosition position = readPosition(bytes); + WorkflowModel model = readModel(bytes); + instance.restore(position, model, context, startDate); + return instance; + } + } + + private WorkflowPosition readPosition(byte[] bytes) { + // TODO read position + return null; + } + + private void writePosition(WorkflowPosition position) { + // TODO write position + } + + private WorkflowModel readModel(byte[] value) { + // TODO read model + return null; + } + + private void writeModel(WorkflowModel model) { + // TODO write model + } + + @Override + protected byte[] marshall(WorkflowContextData workflowContext, TaskContextData taskContext) { + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + try (WorkflowOutputBuffer writer = factory.output(bytes)) { + WorkflowInstanceData instance = workflowContext.instanceData(); + writer.writeString(instance.id()); + writer.writeEnum(instance.status()); + writeModel(workflowContext.instanceData().input()); + writer.writeInstant(instance.startedAt()); + writeModel(workflowContext.context()); + writePosition(taskContext.position()); + writeModel(taskContext.input()); + } + return bytes.toByteArray(); + } +} diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/DefaultBufferFactory.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/DefaultBufferFactory.java new file mode 100644 index 00000000..26c03b38 --- /dev/null +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/DefaultBufferFactory.java @@ -0,0 +1,32 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence.bigmap; + +import java.io.InputStream; +import java.io.OutputStream; + +public class DefaultBufferFactory implements WorkflowBufferFactory { + + @Override + public WorkflowInputBuffer input(InputStream input) { + return new DefaultInputBuffer(input); + } + + @Override + public WorkflowOutputBuffer output(OutputStream output) { + return new DefaultOutputBuffer(output); + } +} diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/DefaultInputBuffer.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/DefaultInputBuffer.java new file mode 100644 index 00000000..915e5aa7 --- /dev/null +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/DefaultInputBuffer.java @@ -0,0 +1,131 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence.bigmap; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.time.Instant; + +public class DefaultInputBuffer implements WorkflowInputBuffer { + + private DataInputStream input; + + public DefaultInputBuffer(InputStream in) { + input = new DataInputStream(in); + } + + @Override + public String readString() { + try { + return input.readUTF(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public int readInt() { + try { + return input.readInt(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public short readShort() { + try { + return input.readShort(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public long readLong() { + try { + return input.readLong(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public float readFloat() { + try { + return input.readFloat(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public double readDouble() { + try { + return input.readFloat(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public boolean readBoolean() { + try { + return input.readBoolean(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public byte readByte() { + try { + return input.readByte(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public byte[] readBytes() { + try { + return input.readNBytes(readInt()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public > T readEnum(Class enumClass) { + return Enum.valueOf(enumClass, readString()); + } + + @Override + public Instant readInstant() { + return Instant.ofEpochMilli(readLong()); + } + + @Override + public void close() { + try { + input.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/DefaultOutputBuffer.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/DefaultOutputBuffer.java new file mode 100644 index 00000000..50ec4129 --- /dev/null +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/DefaultOutputBuffer.java @@ -0,0 +1,144 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.persistence.bigmap; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.time.Instant; + +public class DefaultOutputBuffer implements WorkflowOutputBuffer { + + private DataOutputStream output; + + public DefaultOutputBuffer(OutputStream out) { + output = new DataOutputStream(out); + } + + @Override + public WorkflowOutputBuffer writeString(String text) { + try { + output.writeUTF(text); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return this; + } + + @Override + public WorkflowOutputBuffer writeInt(int number) { + try { + output.writeInt(number); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return this; + } + + @Override + public WorkflowOutputBuffer writeShort(short number) { + try { + output.writeShort(number); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return this; + } + + @Override + public WorkflowOutputBuffer writeLong(long number) { + try { + output.writeLong(number); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return this; + } + + @Override + public WorkflowOutputBuffer writeFloat(float number) { + try { + output.writeFloat(number); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return this; + } + + @Override + public WorkflowOutputBuffer writeDouble(double number) { + try { + output.writeDouble(number); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return this; + } + + @Override + public WorkflowOutputBuffer writeBoolean(boolean bool) { + try { + output.writeBoolean(bool); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return this; + } + + @Override + public WorkflowOutputBuffer writeByte(byte one) { + try { + output.writeByte(one); + ; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return this; + } + + @Override + public WorkflowOutputBuffer writeBytes(byte[] bytes) { + try { + writeInt(bytes.length); + output.write(bytes); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return this; + } + + @Override + public WorkflowOutputBuffer writeInstant(Instant instant) { + writeLong(instant.getEpochSecond()); + return this; + } + + @Override + public > WorkflowOutputBuffer writeEnum(T value) { + writeString(value.name()); + return this; + } + + @Override + public void close() { + try { + output.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/WorkflowBufferFactory.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/WorkflowBufferFactory.java new file mode 100644 index 00000000..9466d8f3 --- /dev/null +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/WorkflowBufferFactory.java @@ -0,0 +1,26 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence.bigmap; + +import java.io.InputStream; +import java.io.OutputStream; + +public interface WorkflowBufferFactory { + + WorkflowInputBuffer input(InputStream input); + + WorkflowOutputBuffer output(OutputStream output); +} diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/WorkflowInputBuffer.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/WorkflowInputBuffer.java new file mode 100644 index 00000000..dd1ddedd --- /dev/null +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/WorkflowInputBuffer.java @@ -0,0 +1,46 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence.bigmap; + +import java.io.Closeable; +import java.time.Instant; + +public interface WorkflowInputBuffer extends Closeable { + + String readString(); + + int readInt(); + + short readShort(); + + long readLong(); + + float readFloat(); + + double readDouble(); + + boolean readBoolean(); + + byte readByte(); + + byte[] readBytes(); + + > T readEnum(Class enumClass); + + Instant readInstant(); + + void close(); +} diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/WorkflowOutputBuffer.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/WorkflowOutputBuffer.java new file mode 100644 index 00000000..ea49d15f --- /dev/null +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/WorkflowOutputBuffer.java @@ -0,0 +1,46 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence.bigmap; + +import java.io.Closeable; +import java.time.Instant; + +public interface WorkflowOutputBuffer extends Closeable { + + WorkflowOutputBuffer writeString(String text); + + WorkflowOutputBuffer writeInt(int number); + + WorkflowOutputBuffer writeShort(short number); + + WorkflowOutputBuffer writeLong(long number); + + WorkflowOutputBuffer writeFloat(float number); + + WorkflowOutputBuffer writeDouble(double number); + + WorkflowOutputBuffer writeBoolean(boolean bool); + + WorkflowOutputBuffer writeByte(byte one); + + WorkflowOutputBuffer writeBytes(byte[] bytes); + + WorkflowOutputBuffer writeInstant(Instant instant); + + > WorkflowOutputBuffer writeEnum(T value); + + void close(); +} diff --git a/impl/persistence/mvstore/pom.xml b/impl/persistence/mvstore/pom.xml new file mode 100644 index 00000000..94892b91 --- /dev/null +++ b/impl/persistence/mvstore/pom.xml @@ -0,0 +1,21 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-persistence + 8.0.0-SNAPSHOT + + serverlessworkflow-persistence-mvstore + Serverless Workflow :: Impl :: Pesistence:: MVStore + + + io.serverlessworkflow + serverlessworkflow-persistence-big-map + + + com.h2database + h2-mvstore + 1.4.199 + + + \ No newline at end of file diff --git a/impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStorePersistenceStore.java b/impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStorePersistenceStore.java new file mode 100644 index 00000000..35ce17fd --- /dev/null +++ b/impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStorePersistenceStore.java @@ -0,0 +1,50 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence.mvstore; + +import io.serverlessworkflow.api.types.Document; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.WorkflowDefinitionData; +import io.serverlessworkflow.impl.persistence.bigmap.BytesBigMapPersistenceStore; +import java.util.Map; +import org.h2.mvstore.MVStore; + +public class MVStorePersistenceStore extends BytesBigMapPersistenceStore { + private final MVStore mvStore; + + protected static final String ID_SEPARATOR = "-"; + + public MVStorePersistenceStore(String dbName) { + this.mvStore = MVStore.open(dbName); + } + + protected static String identifier(Workflow workflow, String sep) { + Document document = workflow.getDocument(); + return document.getNamespace() + sep + document.getName() + sep + document.getVersion(); + } + + @Override + protected Map instances(WorkflowDefinitionData definition) { + return mvStore.openMap(identifier(definition.workflow(), ID_SEPARATOR)); + } + + @Override + public void close() { + if (!mvStore.isClosed()) { + mvStore.close(); + } + } +} diff --git a/impl/persistence/pom.xml b/impl/persistence/pom.xml new file mode 100644 index 00000000..71814078 --- /dev/null +++ b/impl/persistence/pom.xml @@ -0,0 +1,16 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-impl + 8.0.0-SNAPSHOT + + serverlessworkflow-persistence + Serverless Workflow :: Implementation:: Persistence + pom + + mvstore + bigmap + api + + diff --git a/impl/pom.xml b/impl/pom.xml index 41c4cb73..352f8553 100644 --- a/impl/pom.xml +++ b/impl/pom.xml @@ -26,6 +26,16 @@ serverlessworkflow-impl-http ${project.version} + + io.serverlessworkflow + serverlessworkflow-persistence-api + ${project.version} + + + io.serverlessworkflow + serverlessworkflow-persistence-big-map + ${project.version} + io.serverlessworkflow serverlessworkflow-impl-jackson-jwt @@ -70,6 +80,7 @@ core jackson jwt-impl + persistence test \ No newline at end of file From 94c645b5d23466812e94aa0e7830967fb86038d5 Mon Sep 17 00:00:00 2001 From: fjtirado Date: Tue, 30 Sep 2025 14:29:36 +0200 Subject: [PATCH 2/7] [Fix #782] Refining approach Signed-off-by: fjtirado --- .../serverlessworkflow/impl/TaskContext.java | 6 + .../impl/WorkflowApplication.java | 10 +- .../impl/WorkflowDefinition.java | 14 +- .../impl/WorkflowMutableInstance.java | 74 +++++----- .../impl/executors/AbstractTaskExecutor.java | 10 +- .../lifecycle/WorkflowCompletedEvent.java | 10 +- .../ce/AbstractLifeCyclePublisher.java | 7 +- impl/jackson/pom.xml | 2 +- impl/persistence/api/pom.xml | 5 +- .../impl/marshaller/AbstractInputBuffer.java | 137 ++++++++++++++++++ .../impl/marshaller/AbstractOutputBuffer.java | 126 ++++++++++++++++ .../marshaller/CustomObjectMarshaller.java | 24 +++ .../marshaller}/DefaultBufferFactory.java | 13 +- .../impl/marshaller}/DefaultInputBuffer.java | 19 +-- .../impl/marshaller}/DefaultOutputBuffer.java | 23 +-- .../impl/marshaller/Type.java | 32 ++++ .../marshaller}/WorkflowBufferFactory.java | 2 +- .../impl/marshaller}/WorkflowInputBuffer.java | 10 +- .../marshaller}/WorkflowOutputBuffer.java | 13 +- ...ceReader.java => PersistenceTaskInfo.java} | 16 +- .../persistence/PersistenceWorkflowInfo.java | 29 ++++ .../WorkflowPersistenceApplication.java | 88 ----------- .../WorkflowPersistenceInstance.java | 58 ++++++++ .../WorkflowPersistenceListener.java | 22 ++- ....java => WorkflowPersistenceRestorer.java} | 16 +- .../WorkflowPersistenceWriter.java | 4 +- impl/persistence/bigmap/pom.xml | 2 +- ...re.java => BigMapIdPersistenceWriter.java} | 19 +-- .../bigmap/BigMapPersistenceRestorer.java | 116 +++++++++++++++ .../bigmap/BigMapPersistenceStore.java | 62 +------- .../bigmap/BigMapPersistenceWriter.java | 109 ++++++++++++++ .../bigmap/BytesBigMapApplicationBuilder.java | 61 ++++++++ .../BytesBigMapPersistenceRestorer.java | 78 ++++++++++ .../bigmap/BytesBigMapPersistenceStore.java | 95 ------------ .../bigmap/BytesBigMapPersistenceWriter.java | 100 +++++++++++++ .../persistence/bigmap/MarshallingUtils.java | 28 ++++ .../bigmap/PersistenceInstanceInfo.java | 21 +++ impl/persistence/jackson-marshaller/pom.xml | 20 +++ .../jackson/JacksonModelMarshaller.java | 40 +++++ impl/persistence/mvstore/pom.xml | 2 +- .../mvstore/MVStorePersistenceStore.java | 44 +++++- 41 files changed, 1185 insertions(+), 382 deletions(-) create mode 100644 impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/AbstractInputBuffer.java create mode 100644 impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/AbstractOutputBuffer.java create mode 100644 impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/CustomObjectMarshaller.java rename impl/persistence/{bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap => api/src/main/java/io/serverlessworkflow/impl/marshaller}/DefaultBufferFactory.java (70%) rename impl/persistence/{bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap => api/src/main/java/io/serverlessworkflow/impl/marshaller}/DefaultInputBuffer.java (85%) rename impl/persistence/{bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap => api/src/main/java/io/serverlessworkflow/impl/marshaller}/DefaultOutputBuffer.java (85%) create mode 100644 impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/Type.java rename impl/persistence/{bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap => api/src/main/java/io/serverlessworkflow/impl/marshaller}/WorkflowBufferFactory.java (93%) rename impl/persistence/{bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap => api/src/main/java/io/serverlessworkflow/impl/marshaller}/WorkflowInputBuffer.java (84%) rename impl/persistence/{bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap => api/src/main/java/io/serverlessworkflow/impl/marshaller}/WorkflowOutputBuffer.java (78%) rename impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/{WorkflowMinimumPersistenceReader.java => PersistenceTaskInfo.java} (63%) create mode 100644 impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceWorkflowInfo.java delete mode 100644 impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceApplication.java create mode 100644 impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceInstance.java rename impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/{WorkflowIdPersistentReader.java => WorkflowPersistenceRestorer.java} (67%) rename impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/{BigMapIdPersistenceStore.java => BigMapIdPersistenceWriter.java} (59%) create mode 100644 impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceRestorer.java create mode 100644 impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceWriter.java create mode 100644 impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapApplicationBuilder.java create mode 100644 impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceRestorer.java delete mode 100644 impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceStore.java create mode 100644 impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceWriter.java create mode 100644 impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/MarshallingUtils.java create mode 100644 impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/PersistenceInstanceInfo.java create mode 100644 impl/persistence/jackson-marshaller/pom.xml create mode 100644 impl/persistence/jackson-marshaller/src/main/java/io/serverlessworkflow/impl/marshaller/jackson/JacksonModelMarshaller.java diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/TaskContext.java b/impl/core/src/main/java/io/serverlessworkflow/impl/TaskContext.java index 152a3f61..9634c5f7 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/TaskContext.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/TaskContext.java @@ -37,6 +37,7 @@ public class TaskContext implements TaskContextData { private WorkflowModel rawOutput; private Instant completedAt; private TransitionInfo transition; + private boolean completed; public TaskContext( WorkflowModel input, @@ -109,6 +110,7 @@ public WorkflowModel rawOutput() { public TaskContext output(WorkflowModel output) { this.output = output; + this.completed = true; return this; } @@ -159,6 +161,10 @@ public TaskContext transition(TransitionInfo transition) { return this; } + public boolean isCompleted() { + return completed; + } + @Override public String toString() { return "TaskContext [position=" 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 93e89aba..8e25e651 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java @@ -60,7 +60,7 @@ public class WorkflowApplication implements AutoCloseable { private final Collection eventPublishers; private final boolean lifeCycleCEPublishingEnabled; - protected WorkflowApplication(Builder builder) { + private WorkflowApplication(Builder builder) { this.taskFactory = builder.taskFactory; this.exprFactory = builder.exprFactory; this.resourceLoaderFactory = builder.resourceLoaderFactory; @@ -148,7 +148,7 @@ public SchemaValidator getValidator(SchemaInline inline) { () -> new RuntimeDescriptor("reference impl", "1.0.0_alpha", Collections.emptyMap()); private boolean lifeCycleCEPublishingEnabled = true; - protected Builder() {} + private Builder() {} public Builder withListener(WorkflowExecutionListener listener) { listeners.add(listener); @@ -256,11 +256,7 @@ public Map workflowDefinitions() { public WorkflowDefinition workflowDefinition(Workflow workflow) { return definitions.computeIfAbsent( - WorkflowDefinitionId.of(workflow), k -> createDefinition(workflow)); - } - - protected WorkflowDefinition createDefinition(Workflow workflow) { - return WorkflowDefinition.of(this, workflow); + WorkflowDefinitionId.of(workflow), k -> WorkflowDefinition.of(this, workflow)); } @Override diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java index cc964009..b79d86bb 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowDefinition.java @@ -25,6 +25,8 @@ import io.serverlessworkflow.impl.resources.ResourceLoader; import io.serverlessworkflow.impl.schema.SchemaValidator; import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; public class WorkflowDefinition implements AutoCloseable, WorkflowDefinitionData { @@ -37,6 +39,7 @@ public class WorkflowDefinition implements AutoCloseable, WorkflowDefinitionData private final WorkflowApplication application; private final TaskExecutor taskExecutor; private final ResourceLoader resourceLoader; + private final Map> executors = new HashMap<>(); private WorkflowDefinition( WorkflowApplication application, Workflow workflow, ResourceLoader resourceLoader) { @@ -72,8 +75,7 @@ static WorkflowDefinition of(WorkflowApplication application, Workflow workflow, public WorkflowInstance instance(Object input) { WorkflowModel inputModel = application.modelFactory().fromAny(input); inputSchemaValidator().ifPresent(v -> v.validate(inputModel)); - return new WorkflowMutableInstance( - this, application().idFactory().get(), inputModel, WorkflowStatus.PENDING); + return new WorkflowMutableInstance(this, application().idFactory().get(), inputModel); } Optional inputSchemaValidator() { @@ -110,6 +112,14 @@ public ResourceLoader resourceLoader() { return resourceLoader; } + public TaskExecutor taskExecutor(String jsonPointer) { + return executors.get(jsonPointer); + } + + public void addTaskExecutor(WorkflowMutablePosition position, TaskExecutor taskExecutor) { + executors.put(position.jsonPointer(), taskExecutor); + } + @Override public void close() {} } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowMutableInstance.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowMutableInstance.java index 63f98872..0cea457b 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowMutableInstance.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowMutableInstance.java @@ -38,32 +38,43 @@ public class WorkflowMutableInstance implements WorkflowInstance { protected final AtomicReference status; - private final String id; - private final WorkflowModel input; + protected final String id; + protected final WorkflowModel input; + + protected final WorkflowContext workflowContext; + protected Instant startedAt; + + protected AtomicReference> futureRef = new AtomicReference<>(); + protected Instant completedAt; - private WorkflowContext workflowContext; - private Instant startedAt; - private Instant completedAt; - private volatile WorkflowModel output; private Lock statusLock = new ReentrantLock(); - private CompletableFuture completableFuture; private Map, TaskContext> suspended; - public WorkflowMutableInstance( - WorkflowDefinition definition, String id, WorkflowModel input, WorkflowStatus status) { + public WorkflowMutableInstance(WorkflowDefinition definition, String id, WorkflowModel input) { this.id = id; this.input = input; - this.status = new AtomicReference<>(status); + this.status = new AtomicReference<>(WorkflowStatus.PENDING); this.workflowContext = new WorkflowContext(definition, this); } @Override public CompletableFuture start() { - startedAt = Instant.now(); - status.set(WorkflowStatus.RUNNING); - publishEvent( - workflowContext, l -> l.onWorkflowStarted(new WorkflowStartedEvent(workflowContext))); - this.completableFuture = + return startExecution( + () -> { + startedAt = Instant.now(); + status.set(WorkflowStatus.RUNNING); + publishEvent( + workflowContext, l -> l.onWorkflowStarted(new WorkflowStartedEvent(workflowContext))); + }); + } + + protected final CompletableFuture startExecution(Runnable runnable) { + CompletableFuture future = futureRef.get(); + if (future != null) { + return future; + } + runnable.run(); + future = TaskExecutorHelper.processTaskList( workflowContext.definition().startTask(), workflowContext, @@ -75,7 +86,8 @@ public CompletableFuture start() { .orElse(input)) .whenComplete(this::whenFailed) .thenApply(this::whenSuccess); - return completableFuture; + futureRef.set(future); + return future; } private void whenFailed(WorkflowModel result, Throwable ex) { @@ -94,7 +106,7 @@ private void handleException(Throwable ex) { } private WorkflowModel whenSuccess(WorkflowModel node) { - output = + WorkflowModel output = workflowContext .definition() .outputFilter() @@ -103,7 +115,8 @@ private WorkflowModel whenSuccess(WorkflowModel node) { workflowContext.definition().outputSchemaValidator().ifPresent(v -> v.validate(output)); status.set(WorkflowStatus.COMPLETED); publishEvent( - workflowContext, l -> l.onWorkflowCompleted(new WorkflowCompletedEvent(workflowContext))); + workflowContext, + l -> l.onWorkflowCompleted(new WorkflowCompletedEvent(workflowContext, output))); return output; } @@ -134,19 +147,17 @@ public WorkflowStatus status() { @Override public WorkflowModel output() { - return output; + return futureRef.get().join(); } @Override public T outputAs(Class clazz) { - return output != null - ? output - .as(clazz) - .orElseThrow( - () -> - new IllegalArgumentException( - "Output " + output + " cannot be converted to class " + clazz)) - : null; + return output() + .as(clazz) + .orElseThrow( + () -> + new IllegalArgumentException( + "Output " + output() + " cannot be converted to class " + clazz)); } public void status(WorkflowStatus state) { @@ -236,13 +247,6 @@ public CompletableFuture suspendedCheck(TaskContext t) { return CompletableFuture.completedFuture(t); } - // internal purposes only, not to be invoked directly by users of the API - public void restore( - WorkflowPosition position, WorkflowModel model, WorkflowModel context, Instant startedAt) { - this.startedAt = startedAt; - workflowContext.context(context); - } - @Override public boolean cancel() { try { @@ -260,4 +264,6 @@ public boolean cancel() { statusLock.unlock(); } } + + public void restoreContext(WorkflowDefinition definition, TaskContext context) {} } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java index ba3cae0d..e0242f32 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java @@ -78,11 +78,13 @@ public abstract static class AbstractTaskExecutorBuilder< protected final WorkflowApplication application; protected final Workflow workflow; protected final ResourceLoader resourceLoader; + private final WorkflowDefinition definition; private V instance; protected AbstractTaskExecutorBuilder( WorkflowMutablePosition position, T task, WorkflowDefinition definition) { + this.definition = definition; this.workflow = definition.workflow(); this.taskName = position.last().toString(); this.position = position; @@ -147,6 +149,7 @@ public V build() { if (instance == null) { instance = buildInstance(); buildTransition(instance); + definition.addTaskExecutor(position, instance); } return instance; } @@ -189,8 +192,9 @@ private CompletableFuture executeNext( public CompletableFuture apply( WorkflowContext workflowContext, Optional parentContext, WorkflowModel input) { TaskContext taskContext = new TaskContext(input, position, parentContext, taskName, task); + workflowContext.instance().restoreContext(workflowContext.definition(), taskContext); CompletableFuture completable = CompletableFuture.completedFuture(taskContext); - if (!TaskExecutorHelper.isActive(workflowContext)) { + if (taskContext.isCompleted() && !TaskExecutorHelper.isActive(workflowContext)) { return completable; } if (ifFilter.map(f -> f.test(workflowContext, taskContext, input)).orElse(true)) { @@ -256,6 +260,10 @@ private void handleException( } } + public WorkflowPosition position() { + return position; + } + protected abstract TransitionInfo getSkipTransition(); protected abstract CompletableFuture execute( diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/lifecycle/WorkflowCompletedEvent.java b/impl/core/src/main/java/io/serverlessworkflow/impl/lifecycle/WorkflowCompletedEvent.java index 727a28e7..cae1a6d0 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/lifecycle/WorkflowCompletedEvent.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/lifecycle/WorkflowCompletedEvent.java @@ -16,10 +16,18 @@ package io.serverlessworkflow.impl.lifecycle; import io.serverlessworkflow.impl.WorkflowContextData; +import io.serverlessworkflow.impl.WorkflowModel; public class WorkflowCompletedEvent extends WorkflowEvent { - public WorkflowCompletedEvent(WorkflowContextData workflow) { + private WorkflowModel output; + + public WorkflowCompletedEvent(WorkflowContextData workflow, WorkflowModel output) { super(workflow); + this.output = output; + } + + public WorkflowModel output() { + return output; } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/lifecycle/ce/AbstractLifeCyclePublisher.java b/impl/core/src/main/java/io/serverlessworkflow/impl/lifecycle/ce/AbstractLifeCyclePublisher.java index ade29db3..374c6751 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/lifecycle/ce/AbstractLifeCyclePublisher.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/lifecycle/ce/AbstractLifeCyclePublisher.java @@ -225,7 +225,8 @@ public void onWorkflowCompleted(WorkflowCompletedEvent event) { builder() .withData( cloudEventData( - new WorkflowCompletedCEData(id(ev), ref(ev), ev.eventDate(), output(ev)), + new WorkflowCompletedCEData( + id(ev), ref(ev), ev.eventDate(), from(event.output())), this::convert)) .withType(WORKFLOW_COMPLETED) .build()); @@ -328,10 +329,6 @@ private static String pos(TaskEvent ev) { return ev.taskContext().position().jsonPointer(); } - private static Object output(WorkflowEvent ev) { - return from(ev.workflowContext().instanceData().output()); - } - private static Object output(TaskEvent ev) { return from(ev.taskContext().output()); } diff --git a/impl/jackson/pom.xml b/impl/jackson/pom.xml index d0a1f90c..fb1d1499 100644 --- a/impl/jackson/pom.xml +++ b/impl/jackson/pom.xml @@ -6,7 +6,7 @@ 8.0.0-SNAPSHOT serverlessworkflow-impl-jackson - Serverless Workflow :: Impl :: HTTP + Serverless Workflow :: Impl :: Jackson io.serverlessworkflow diff --git a/impl/persistence/api/pom.xml b/impl/persistence/api/pom.xml index 79c165a4..f67a12e6 100644 --- a/impl/persistence/api/pom.xml +++ b/impl/persistence/api/pom.xml @@ -6,11 +6,12 @@ 8.0.0-SNAPSHOT serverlessworkflow-persistence-api - Serverless Workflow :: Impl :: Pesistence:: API + Serverless Workflow :: Impl :: Persistence:: API io.serverlessworkflow serverlessworkflow-impl-core - + 8.0.0-SNAPSHOT + \ No newline at end of file diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/AbstractInputBuffer.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/AbstractInputBuffer.java new file mode 100644 index 00000000..746e7a67 --- /dev/null +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/AbstractInputBuffer.java @@ -0,0 +1,137 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.marshaller; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +public abstract class AbstractInputBuffer implements WorkflowInputBuffer { + + private final Collection customMarshallers; + + protected AbstractInputBuffer(Collection customMarshallers) { + this.customMarshallers = customMarshallers; + } + + @Override + public > T readEnum(Class enumClass) { + return Enum.valueOf(enumClass, readString()); + } + + @Override + public Instant readInstant() { + return Instant.ofEpochMilli(readLong()); + } + + @Override + public Map readMap() { + int size = readInt(); + Map map = new LinkedHashMap(size); + while (size-- > 0) { + map.put(readString(), readObject()); + } + return map; + } + + @Override + public Collection readCollection() { + int size = readInt(); + Collection col = new ArrayList<>(size); + while (size-- > 0) { + col.add(readObject()); + } + return col; + } + + protected Type readType() { + return Type.values()[readByte()]; + } + + @Override + public Object readObject() { + + Type type = readType(); + + switch (type) { + case NULL: + return null; + + case SHORT: + return readShort(); + + case LONG: + return readLong(); + + case INT: + return readInt(); + + case BYTE: + return readByte(); + + case BYTES: + return readBytes(); + + case FLOAT: + return readFloat(); + + case DOUBLE: + return readDouble(); + + case BOOLEAN: + return readBoolean(); + + case STRING: + return readString(); + + case MAP: + return readMap(); + + case COLLECTION: + return readCollection(); + + case INSTANT: + return readInstant(); + + default: + return readCustomObject(); + } + } + + protected Class readClass() { + String className = readString(); + try { + return Class.forName(className); + } catch (ClassNotFoundException ex) { + throw new IllegalStateException(ex); + } + } + + protected Class loadClass(String className) throws ClassNotFoundException { + return Class.forName(className); + } + + protected Object readCustomObject() { + Class objectClass = readClass(); + return customMarshallers.stream() + .filter(m -> m.getObjectClass().isAssignableFrom(objectClass)) + .findFirst() + .map(m -> m.read(this)) + .orElseThrow(() -> new IllegalArgumentException("Unsupported type " + objectClass)); + } +} diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/AbstractOutputBuffer.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/AbstractOutputBuffer.java new file mode 100644 index 00000000..3b368b53 --- /dev/null +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/AbstractOutputBuffer.java @@ -0,0 +1,126 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.marshaller; + +import java.time.Instant; +import java.util.Collection; +import java.util.Map; + +public abstract class AbstractOutputBuffer implements WorkflowOutputBuffer { + + private final Collection customMarshallers; + + protected AbstractOutputBuffer(Collection customMarshallers) { + this.customMarshallers = customMarshallers; + } + + @Override + public WorkflowOutputBuffer writeInstant(Instant instant) { + writeLong(instant.getEpochSecond()); + return this; + } + + @Override + public > WorkflowOutputBuffer writeEnum(T value) { + writeString(value.name()); + return this; + } + + @Override + public WorkflowOutputBuffer writeMap(Map map) { + writeInt(map.size()); + map.forEach( + (k, v) -> { + writeString(k); + writeObject(v); + }); + + return this; + } + + @Override + public WorkflowOutputBuffer writeCollection(Collection col) { + writeInt(col.size()); + col.forEach(this::writeObject); + return this; + } + + @Override + public WorkflowOutputBuffer writeObject(Object object) { + if (object == null) { + writeType(Type.NULL); + } else if (object instanceof Short number) { + writeType(Type.SHORT); + writeShort(number); + } else if (object instanceof Integer number) { + writeType(Type.INT); + writeInt(number); + } else if (object instanceof Long number) { + writeType(Type.LONG); + writeLong(number); + } else if (object instanceof Byte number) { + writeType(Type.BYTE); + writeLong(number); + } else if (object instanceof Float number) { + writeType(Type.FLOAT); + writeFloat(number); + } else if (object instanceof Double number) { + writeType(Type.DOUBLE); + writeDouble(number); + } else if (object instanceof Boolean bool) { + writeType(Type.BOOLEAN); + writeBoolean(bool); + } else if (object instanceof String str) { + writeType(Type.STRING); + writeString(str); + } else if (object instanceof Map value) { + writeType(Type.MAP); + writeMap(value); + } else if (object instanceof Collection value) { + writeType(Type.COLLECTION); + writeCollection(value); + } else if (object instanceof Instant value) { + writeType(Type.INSTANT); + writeInstant(value); + } else if (object instanceof byte[] bytes) { + writeType(Type.BYTES); + writeBytes(bytes); + } else { + writeCustomObject(object); + } + return this; + } + + protected void writeClass(Class objectClass) { + writeString(objectClass.getCanonicalName()); + } + + protected void writeCustomObject(Object object) { + customMarshallers.stream() + .filter(m -> m.getObjectClass().isAssignableFrom(object.getClass())) + .findFirst() + .ifPresentOrElse( + m -> { + writeClass(m.getObjectClass()); + m.write(this, m.getObjectClass().cast(object)); + }, + () -> new IllegalArgumentException("Unsupported type " + object.getClass())); + } + + protected void writeType(Type type) { + writeByte((byte) type.ordinal()); + } +} diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/CustomObjectMarshaller.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/CustomObjectMarshaller.java new file mode 100644 index 00000000..b96283b6 --- /dev/null +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/CustomObjectMarshaller.java @@ -0,0 +1,24 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.marshaller; + +public interface CustomObjectMarshaller { + void write(WorkflowOutputBuffer buffer, T object); + + T read(WorkflowInputBuffer buffer); + + Class getObjectClass(); +} diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/DefaultBufferFactory.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/DefaultBufferFactory.java similarity index 70% rename from impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/DefaultBufferFactory.java rename to impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/DefaultBufferFactory.java index 26c03b38..54b27cfd 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/DefaultBufferFactory.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/DefaultBufferFactory.java @@ -13,20 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.serverlessworkflow.impl.persistence.bigmap; +package io.serverlessworkflow.impl.marshaller; import java.io.InputStream; import java.io.OutputStream; +import java.util.Collection; public class DefaultBufferFactory implements WorkflowBufferFactory { + private final Collection marshallers; + + public DefaultBufferFactory(Collection marshallers) { + this.marshallers = marshallers; + } + @Override public WorkflowInputBuffer input(InputStream input) { - return new DefaultInputBuffer(input); + return new DefaultInputBuffer(input, marshallers); } @Override public WorkflowOutputBuffer output(OutputStream output) { - return new DefaultOutputBuffer(output); + return new DefaultOutputBuffer(output, marshallers); } } diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/DefaultInputBuffer.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/DefaultInputBuffer.java similarity index 85% rename from impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/DefaultInputBuffer.java rename to impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/DefaultInputBuffer.java index 915e5aa7..ac89fbe0 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/DefaultInputBuffer.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/DefaultInputBuffer.java @@ -13,19 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.serverlessworkflow.impl.persistence.bigmap; +package io.serverlessworkflow.impl.marshaller; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; -import java.time.Instant; +import java.util.Collection; -public class DefaultInputBuffer implements WorkflowInputBuffer { +public class DefaultInputBuffer extends AbstractInputBuffer { private DataInputStream input; - public DefaultInputBuffer(InputStream in) { + public DefaultInputBuffer(InputStream in, Collection marshallers) { + super(marshallers); input = new DataInputStream(in); } @@ -110,16 +111,6 @@ public byte[] readBytes() { } } - @Override - public > T readEnum(Class enumClass) { - return Enum.valueOf(enumClass, readString()); - } - - @Override - public Instant readInstant() { - return Instant.ofEpochMilli(readLong()); - } - @Override public void close() { try { diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/DefaultOutputBuffer.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/DefaultOutputBuffer.java similarity index 85% rename from impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/DefaultOutputBuffer.java rename to impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/DefaultOutputBuffer.java index 50ec4129..1d519279 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/DefaultOutputBuffer.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/DefaultOutputBuffer.java @@ -13,19 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.serverlessworkflow.impl.persistence.bigmap; +package io.serverlessworkflow.impl.marshaller; import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UncheckedIOException; -import java.time.Instant; +import java.util.Collection; -public class DefaultOutputBuffer implements WorkflowOutputBuffer { +public class DefaultOutputBuffer extends AbstractOutputBuffer { private DataOutputStream output; - public DefaultOutputBuffer(OutputStream out) { + public DefaultOutputBuffer( + OutputStream out, Collection customMarshallers) { + super(customMarshallers); output = new DataOutputStream(out); } @@ -103,7 +105,6 @@ public WorkflowOutputBuffer writeBoolean(boolean bool) { public WorkflowOutputBuffer writeByte(byte one) { try { output.writeByte(one); - ; } catch (IOException e) { throw new UncheckedIOException(e); } @@ -121,18 +122,6 @@ public WorkflowOutputBuffer writeBytes(byte[] bytes) { return this; } - @Override - public WorkflowOutputBuffer writeInstant(Instant instant) { - writeLong(instant.getEpochSecond()); - return this; - } - - @Override - public > WorkflowOutputBuffer writeEnum(T value) { - writeString(value.name()); - return this; - } - @Override public void close() { try { diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/Type.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/Type.java new file mode 100644 index 00000000..9183c52c --- /dev/null +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/Type.java @@ -0,0 +1,32 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.marshaller; + +enum Type { + BYTE, + BYTES, + INT, + SHORT, + LONG, + FLOAT, + DOUBLE, + BOOLEAN, + STRING, + INSTANT, + MAP, + COLLECTION, + NULL +} diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/WorkflowBufferFactory.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/WorkflowBufferFactory.java similarity index 93% rename from impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/WorkflowBufferFactory.java rename to impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/WorkflowBufferFactory.java index 9466d8f3..78b27c2b 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/WorkflowBufferFactory.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/WorkflowBufferFactory.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.serverlessworkflow.impl.persistence.bigmap; +package io.serverlessworkflow.impl.marshaller; import java.io.InputStream; import java.io.OutputStream; diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/WorkflowInputBuffer.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/WorkflowInputBuffer.java similarity index 84% rename from impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/WorkflowInputBuffer.java rename to impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/WorkflowInputBuffer.java index dd1ddedd..f45567df 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/WorkflowInputBuffer.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/WorkflowInputBuffer.java @@ -13,10 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.serverlessworkflow.impl.persistence.bigmap; +package io.serverlessworkflow.impl.marshaller; import java.io.Closeable; import java.time.Instant; +import java.util.Collection; +import java.util.Map; public interface WorkflowInputBuffer extends Closeable { @@ -42,5 +44,11 @@ public interface WorkflowInputBuffer extends Closeable { Instant readInstant(); + Map readMap(); + + Collection readCollection(); + + Object readObject(); + void close(); } diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/WorkflowOutputBuffer.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/WorkflowOutputBuffer.java similarity index 78% rename from impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/WorkflowOutputBuffer.java rename to impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/WorkflowOutputBuffer.java index ea49d15f..f7ec25cf 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/WorkflowOutputBuffer.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/WorkflowOutputBuffer.java @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.serverlessworkflow.impl.persistence.bigmap; +package io.serverlessworkflow.impl.marshaller; -import java.io.Closeable; import java.time.Instant; +import java.util.Collection; +import java.util.Map; -public interface WorkflowOutputBuffer extends Closeable { +public interface WorkflowOutputBuffer extends AutoCloseable { WorkflowOutputBuffer writeString(String text); @@ -40,6 +41,12 @@ public interface WorkflowOutputBuffer extends Closeable { WorkflowOutputBuffer writeInstant(Instant instant); + WorkflowOutputBuffer writeMap(Map map); + + WorkflowOutputBuffer writeCollection(Collection col); + + WorkflowOutputBuffer writeObject(Object object); + > WorkflowOutputBuffer writeEnum(T value); void close(); diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowMinimumPersistenceReader.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceTaskInfo.java similarity index 63% rename from impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowMinimumPersistenceReader.java rename to impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceTaskInfo.java index 8f52e4d6..2a127c9a 100644 --- a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowMinimumPersistenceReader.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceTaskInfo.java @@ -15,16 +15,8 @@ */ package io.serverlessworkflow.impl.persistence; -import io.serverlessworkflow.impl.WorkflowDefinition; -import io.serverlessworkflow.impl.WorkflowInstance; -import java.util.stream.Stream; +import io.serverlessworkflow.impl.WorkflowModel; +import java.time.Instant; -public interface WorkflowMinimumPersistenceReader extends AutoCloseable { - - /** - * Allow streaming over all stored workflow instances for a certain definition - * - * @return - */ - Stream all(WorkflowDefinition definition); -} +public record PersistenceTaskInfo( + Instant instant, WorkflowModel model, Boolean isEndNode, String nextPosition) {} diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceWorkflowInfo.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceWorkflowInfo.java new file mode 100644 index 00000000..3bef5bf4 --- /dev/null +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceWorkflowInfo.java @@ -0,0 +1,29 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence; + +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowStatus; +import java.time.Instant; +import java.util.Map; + +public record PersistenceWorkflowInfo( + String id, + Instant startedAt, + WorkflowModel input, + WorkflowModel context, + WorkflowStatus status, + Map tasks) {} diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceApplication.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceApplication.java deleted file mode 100644 index b0d048ed..00000000 --- a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceApplication.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2020-Present The Serverless Workflow Specification Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.serverlessworkflow.impl.persistence; - -import io.serverlessworkflow.api.types.Workflow; -import io.serverlessworkflow.impl.WorkflowApplication; -import io.serverlessworkflow.impl.WorkflowDefinition; -import io.serverlessworkflow.impl.WorkflowInstance; - -public class WorkflowPersistenceApplication - extends WorkflowApplication { - - private final T reader; - private final WorkflowPersistenceWriter writer; - private final boolean resumeAfterReboot; - - protected WorkflowPersistenceApplication(Builder builder) { - super(builder); - this.reader = builder.reader; - this.writer = builder.writer; - this.resumeAfterReboot = builder.resumeAfterReboot; - } - - public T persitenceReader() { - return reader; - } - - public void close() { - super.close(); - try { - reader.close(); - } catch (Exception e) { - } - try { - writer.close(); - } catch (Exception e) { - } - } - - public static Builder builder( - WorkflowPersistenceWriter writer, T reader) { - return new Builder<>(writer, reader); - } - - public static class Builder - extends io.serverlessworkflow.impl.WorkflowApplication.Builder { - - private final WorkflowPersistenceWriter writer; - private final T reader; - private boolean resumeAfterReboot = true; - - protected Builder(WorkflowPersistenceWriter writer, T reader) { - this.writer = writer; - this.reader = reader; - super.withListener(new WorkflowPersistenceListener(writer)); - } - - public Builder resumeAfterReboot(boolean resumeAfterReboot) { - this.resumeAfterReboot = resumeAfterReboot; - return this; - } - - public WorkflowPersistenceApplication build() { - return new WorkflowPersistenceApplication<>(this); - } - } - - protected WorkflowDefinition createDefinition(Workflow workflow) { - WorkflowDefinition definition = super.createDefinition(workflow); - if (resumeAfterReboot) { - reader.all(definition).forEach(WorkflowInstance::resume); - } - return definition; - } -} diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceInstance.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceInstance.java new file mode 100644 index 00000000..8f3921ea --- /dev/null +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceInstance.java @@ -0,0 +1,58 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence; + +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowDefinition; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowMutableInstance; +import io.serverlessworkflow.impl.executors.TransitionInfo; +import java.util.concurrent.CompletableFuture; + +public class WorkflowPersistenceInstance extends WorkflowMutableInstance { + + private final PersistenceWorkflowInfo info; + + public WorkflowPersistenceInstance(WorkflowDefinition definition, PersistenceWorkflowInfo info) { + super(definition, info.id(), info.input()); + this.info = info; + } + + @Override + public CompletableFuture start() { + return startExecution( + () -> { + startedAt = info.startedAt(); + status.set(info.status()); + workflowContext.context(info.context()); + }); + } + + @Override + public void restoreContext(WorkflowDefinition definition, TaskContext context) { + PersistenceTaskInfo taskInfo = info.tasks().get(context.position().jsonPointer()); + if (taskInfo != null) { + context.output(taskInfo.model()); + context.completedAt(taskInfo.instant()); + context.transition( + new TransitionInfo( + taskInfo.nextPosition() == null + ? null + : definition.taskExecutor(taskInfo.nextPosition()), + taskInfo.isEndNode())); + } + } +} diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceListener.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceListener.java index 0ebc47b4..6c1a88e0 100644 --- a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceListener.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceListener.java @@ -15,6 +15,7 @@ */ package io.serverlessworkflow.impl.persistence; +import io.serverlessworkflow.impl.lifecycle.TaskCompletedEvent; import io.serverlessworkflow.impl.lifecycle.TaskStartedEvent; import io.serverlessworkflow.impl.lifecycle.WorkflowCancelledEvent; import io.serverlessworkflow.impl.lifecycle.WorkflowExecutionListener; @@ -25,39 +26,44 @@ public class WorkflowPersistenceListener implements WorkflowExecutionListener { - private final WorkflowPersistenceWriter persistenceStore; + private final WorkflowPersistenceWriter persistenceWriter; public WorkflowPersistenceListener(WorkflowPersistenceWriter persistenceStore) { - this.persistenceStore = persistenceStore; + this.persistenceWriter = persistenceStore; } @Override public void onWorkflowStarted(WorkflowStartedEvent ev) { - persistenceStore.started(ev.workflowContext()); + persistenceWriter.started(ev.workflowContext()); } @Override public void onWorkflowFailed(WorkflowFailedEvent ev) { - persistenceStore.failed(ev.workflowContext(), ev.cause()); + persistenceWriter.failed(ev.workflowContext(), ev.cause()); } @Override public void onWorkflowCancelled(WorkflowCancelledEvent ev) { - persistenceStore.aborted(ev.workflowContext()); + persistenceWriter.aborted(ev.workflowContext()); } @Override public void onWorkflowSuspended(WorkflowSuspendedEvent ev) { - persistenceStore.suspended(ev.workflowContext()); + persistenceWriter.suspended(ev.workflowContext()); } @Override public void onWorkflowResumed(WorkflowResumedEvent ev) { - persistenceStore.resumed(ev.workflowContext()); + persistenceWriter.resumed(ev.workflowContext()); } @Override public void onTaskStarted(TaskStartedEvent ev) { - persistenceStore.updated(ev.workflowContext(), ev.taskContext()); + persistenceWriter.taskStarted(ev.workflowContext(), ev.taskContext()); + } + + @Override + public void onTaskCompleted(TaskCompletedEvent ev) { + persistenceWriter.taskCompleted(ev.workflowContext(), ev.taskContext()); } } diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowIdPersistentReader.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceRestorer.java similarity index 67% rename from impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowIdPersistentReader.java rename to impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceRestorer.java index 2067d7c2..1ee81f39 100644 --- a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowIdPersistentReader.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceRestorer.java @@ -17,15 +17,15 @@ import io.serverlessworkflow.impl.WorkflowDefinition; import io.serverlessworkflow.impl.WorkflowInstance; +import java.util.Collection; +import java.util.Map; import java.util.Optional; -public interface WorkflowIdPersistentReader extends WorkflowMinimumPersistenceReader { +public interface WorkflowPersistenceRestorer extends AutoCloseable { + Map restoreAll(WorkflowDefinition definition); - /** - * Allow recovering by process instance id - * - * @param workflowInstanceId - * @return - */ - Optional findById(WorkflowDefinition definition, String workflowInstanceId); + Map restore( + WorkflowDefinition definition, Collection instanceIds); + + Optional restore(WorkflowDefinition definition, String instanceId); } diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceWriter.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceWriter.java index 4eaaa779..aceef40d 100644 --- a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceWriter.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceWriter.java @@ -32,5 +32,7 @@ public interface WorkflowPersistenceWriter extends AutoCloseable { void resumed(WorkflowContextData workflowContext); - void updated(WorkflowContextData workflowContext, TaskContextData taskContext); + void taskStarted(WorkflowContextData workflowContext, TaskContextData taskContext); + + void taskCompleted(WorkflowContextData workflowContext, TaskContextData taskContext); } diff --git a/impl/persistence/bigmap/pom.xml b/impl/persistence/bigmap/pom.xml index 86948c8f..9ad508a1 100644 --- a/impl/persistence/bigmap/pom.xml +++ b/impl/persistence/bigmap/pom.xml @@ -6,7 +6,7 @@ 8.0.0-SNAPSHOT serverlessworkflow-persistence-big-map - Serverless Workflow :: Impl :: Pesistence:: BigMap + Serverless Workflow :: Impl :: Persistence:: BigMap io.serverlessworkflow diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapIdPersistenceStore.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapIdPersistenceWriter.java similarity index 59% rename from impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapIdPersistenceStore.java rename to impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapIdPersistenceWriter.java index eb3267f5..46cc226e 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapIdPersistenceStore.java +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapIdPersistenceWriter.java @@ -16,23 +16,16 @@ package io.serverlessworkflow.impl.persistence.bigmap; import io.serverlessworkflow.impl.WorkflowContextData; -import io.serverlessworkflow.impl.WorkflowDefinition; -import io.serverlessworkflow.impl.WorkflowInstance; -import io.serverlessworkflow.impl.persistence.WorkflowIdPersistentReader; -import java.util.Optional; -public abstract class BigMapIdPersistenceStore extends BigMapPersistenceStore - implements WorkflowIdPersistentReader { +public abstract class BigMapIdPersistenceWriter + extends BigMapPersistenceWriter { - @Override - protected String key(WorkflowContextData workflowContext) { - return workflowContext.instanceData().id(); + protected BigMapIdPersistenceWriter(BigMapPersistenceStore store) { + super(store); } @Override - public Optional findById( - WorkflowDefinition definition, String workflowInstanceId) { - return Optional.ofNullable(instances(definition).get(workflowInstanceId)) - .map(v -> unmarshall(definition, v)); + protected String key(WorkflowContextData workflowContext) { + return workflowContext.instanceData().id(); } } diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceRestorer.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceRestorer.java new file mode 100644 index 00000000..332a0add --- /dev/null +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceRestorer.java @@ -0,0 +1,116 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence.bigmap; + +import io.serverlessworkflow.impl.WorkflowDefinition; +import io.serverlessworkflow.impl.WorkflowInstance; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowStatus; +import io.serverlessworkflow.impl.persistence.PersistenceTaskInfo; +import io.serverlessworkflow.impl.persistence.PersistenceWorkflowInfo; +import io.serverlessworkflow.impl.persistence.WorkflowPersistenceInstance; +import io.serverlessworkflow.impl.persistence.WorkflowPersistenceRestorer; +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.stream.Collectors; + +public abstract class BigMapPersistenceRestorer implements WorkflowPersistenceRestorer { + + private final BigMapPersistenceStore store; + + protected BigMapPersistenceRestorer(BigMapPersistenceStore store) { + this.store = store; + } + + @Override + public Map restoreAll(WorkflowDefinition definition) { + Map instances = store.instanceData(definition); + Map status = store.status(definition); + Map context = store.context(definition); + return instances.entrySet().stream() + .map( + e -> + restore( + definition, + e.getKey(), + e.getValue(), + store.tasks(e.getKey()), + status.get(e.getKey()), + context.get(e.getKey()))) + .collect(Collectors.toMap(WorkflowInstance::id, i -> i)); + } + + @Override + public Map restore( + WorkflowDefinition definition, Collection instanceIds) { + return instanceIds.stream() + .map(id -> restore(definition, id)) + .flatMap(Optional::stream) + .collect(Collectors.toMap(WorkflowInstance::id, id -> id)); + } + + @Override + public Optional restore(WorkflowDefinition definition, String instanceId) { + Map instances = store.instanceData(definition); + return instances.containsKey(instanceId) + ? Optional.empty() + : Optional.of( + restore( + definition, + instanceId, + instances.get(instanceId), + store.tasks(instanceId), + store.status(definition).get(instanceId), + store.context(definition).get(instanceId))); + } + + public void close() {} + + protected WorkflowInstance restore( + WorkflowDefinition definition, + String instanceId, + V instanceData, + Map tasksData, + S status, + C context) { + return new WorkflowPersistenceInstance( + definition, readPersistenceInfo(instanceId, instanceData, tasksData, status, context)); + } + + protected abstract PersistenceTaskInfo unmarshallTaskInfo(T taskData); + + protected abstract PersistenceInstanceInfo unmarshallInstanceInfo(V instanceData); + + protected abstract WorkflowModel unmarshallContext(C contextData); + + protected abstract WorkflowStatus unmarshallStatus(S statusData); + + protected PersistenceWorkflowInfo readPersistenceInfo( + String instanceId, V instanceData, Map tasksData, S status, C context) { + PersistenceInstanceInfo instanceInfo = unmarshallInstanceInfo(instanceData); + return new PersistenceWorkflowInfo( + instanceId, + instanceInfo.startedAt(), + instanceInfo.input(), + unmarshallContext(context), + unmarshallStatus(status), + tasksData.entrySet().stream() + .collect( + Collectors.toMap(Entry::getKey, entry -> unmarshallTaskInfo(entry.getValue())))); + } +} diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceStore.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceStore.java index 1c387907..689e7cd7 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceStore.java +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceStore.java @@ -15,68 +15,18 @@ */ package io.serverlessworkflow.impl.persistence.bigmap; -import io.serverlessworkflow.impl.TaskContextData; -import io.serverlessworkflow.impl.WorkflowContextData; -import io.serverlessworkflow.impl.WorkflowDefinition; import io.serverlessworkflow.impl.WorkflowDefinitionData; -import io.serverlessworkflow.impl.WorkflowInstance; -import io.serverlessworkflow.impl.persistence.WorkflowMinimumPersistenceReader; -import io.serverlessworkflow.impl.persistence.WorkflowPersistenceWriter; import java.util.Map; -import java.util.stream.Stream; -public abstract class BigMapPersistenceStore - implements WorkflowMinimumPersistenceReader, WorkflowPersistenceWriter { +public interface BigMapPersistenceStore extends AutoCloseable { - @Override - public void started(WorkflowContextData workflowContext) { - instances(workflowContext.definition()).put(key(workflowContext), marshall(workflowContext)); - } + Map instanceData(WorkflowDefinitionData definition); - @Override - public void completed(WorkflowContextData workflowContext) { - instances(workflowContext.definition()).remove(workflowContext.instanceData().id()); - } + Map context(WorkflowDefinitionData workflowContext); - @Override - public void failed(WorkflowContextData workflowContext, Throwable ex) { - instances(workflowContext.definition()).remove(workflowContext.instanceData().id()); - } + Map status(WorkflowDefinitionData workflowContext); - @Override - public void aborted(WorkflowContextData workflowContext) { - instances(workflowContext.definition()).remove(workflowContext.instanceData().id()); - } + Map tasks(K instanceId); - @Override - public void updated(WorkflowContextData workflowContext, TaskContextData taskContext) { - instances(workflowContext.definition()).put(key(workflowContext), marshall(workflowContext)); - } - - @Override - public void suspended(WorkflowContextData workflowContext) { - // nothing to do - } - - @Override - public void resumed(WorkflowContextData workflowContext) { - // nothing to do - } - - @Override - public Stream all(WorkflowDefinition definition) { - return instances(definition).values().stream().map(value -> unmarshall(definition, value)); - } - - protected abstract WorkflowInstance unmarshall(WorkflowDefinition definition, V value); - - protected abstract K key(WorkflowContextData workflowContext); - - protected abstract Map instances(WorkflowDefinitionData definition); - - protected abstract V marshall(WorkflowContextData workflowContext, TaskContextData taskContext); - - protected V marshall(WorkflowContextData workflowContext) { - return marshall(workflowContext, null); - } + void cleanupTasks(K instanceId); } diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceWriter.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceWriter.java new file mode 100644 index 00000000..ec8e2d73 --- /dev/null +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceWriter.java @@ -0,0 +1,109 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence.bigmap; + +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.TaskContextData; +import io.serverlessworkflow.impl.WorkflowContextData; +import io.serverlessworkflow.impl.WorkflowInstanceData; +import io.serverlessworkflow.impl.WorkflowStatus; +import io.serverlessworkflow.impl.persistence.WorkflowPersistenceWriter; + +public abstract class BigMapPersistenceWriter implements WorkflowPersistenceWriter { + + private BigMapPersistenceStore store; + + protected BigMapPersistenceWriter(BigMapPersistenceStore store) { + this.store = store; + } + + @Override + public void started(WorkflowContextData workflowContext) { + store + .instanceData(workflowContext.definition()) + .put(key(workflowContext), marshallInstance(workflowContext.instanceData())); + } + + @Override + public void completed(WorkflowContextData workflowContext) { + removeProcessInstance(workflowContext); + } + + @Override + public void failed(WorkflowContextData workflowContext, Throwable ex) { + removeProcessInstance(workflowContext); + } + + @Override + public void aborted(WorkflowContextData workflowContext) { + removeProcessInstance(workflowContext); + } + + @Override + public void taskStarted(WorkflowContextData workflowContext, TaskContextData taskContext) {} + + @Override + public void taskCompleted(WorkflowContextData workflowContext, TaskContextData taskContext) { + K key = key(workflowContext); + store + .tasks(key) + .put( + taskContext.position().jsonPointer(), + marshallTaskCompleted(workflowContext, (TaskContext) taskContext)); + store.context(workflowContext.definition()).put(key, marshallContext(workflowContext)); + } + + @Override + public void suspended(WorkflowContextData workflowContext) { + store + .status(workflowContext.definition()) + .put(key(workflowContext), marshallStatus(WorkflowStatus.SUSPENDED)); + } + + @Override + public void resumed(WorkflowContextData workflowContext) { + store + .status(workflowContext.definition()) + .put(key(workflowContext), marshallStatus(WorkflowStatus.RUNNING)); + } + + protected void removeProcessInstance(WorkflowContextData workflowContext) { + K key = key(workflowContext); + store.instanceData(workflowContext.definition()).remove(key); + store.context(workflowContext.definition()).remove(key); + store.status(workflowContext.definition()).remove(key); + store.cleanupTasks(key); + } + + protected abstract K key(WorkflowContextData workflowContext); + + protected abstract V marshallInstance(WorkflowInstanceData instance); + + protected abstract C marshallContext(WorkflowContextData workflowContext); + + protected abstract T marshallTaskCompleted( + WorkflowContextData workflowContext, TaskContext taskContext); + + protected abstract S marshallStatus(WorkflowStatus status); + + public void close() { + try { + store.close(); + } catch (Exception e) { + // ignore exception + } + } +} diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapApplicationBuilder.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapApplicationBuilder.java new file mode 100644 index 00000000..388382f0 --- /dev/null +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapApplicationBuilder.java @@ -0,0 +1,61 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence.bigmap; + +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowApplication.Builder; +import io.serverlessworkflow.impl.marshaller.CustomObjectMarshaller; +import io.serverlessworkflow.impl.marshaller.DefaultBufferFactory; +import io.serverlessworkflow.impl.marshaller.WorkflowBufferFactory; +import io.serverlessworkflow.impl.persistence.WorkflowPersistenceListener; +import java.util.ServiceLoader; + +public class BytesBigMapApplicationBuilder { + + public static BytesBigMapApplicationBuilder builder( + WorkflowApplication.Builder builder, + BigMapPersistenceStore store) { + return new BytesBigMapApplicationBuilder(builder, store); + } + + private final BigMapPersistenceStore store; + private final WorkflowApplication.Builder appBuilder; + private WorkflowBufferFactory factory; + + protected BytesBigMapApplicationBuilder( + Builder appBuilder, BigMapPersistenceStore store) { + this.appBuilder = appBuilder; + this.store = store; + } + + public BytesBigMapApplicationBuilder withFactory(WorkflowBufferFactory factory) { + this.factory = factory; + return this; + } + + public WorkflowApplication build() { + if (factory == null) { + factory = + new DefaultBufferFactory( + ServiceLoader.load(CustomObjectMarshaller.class).stream() + .map(ServiceLoader.Provider::get) + .toList()); + } + appBuilder.withListener( + new WorkflowPersistenceListener(new BytesBigMapPersistenceWriter(store, factory))); + return appBuilder.build(); + } +} diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceRestorer.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceRestorer.java new file mode 100644 index 00000000..805bf9ba --- /dev/null +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceRestorer.java @@ -0,0 +1,78 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence.bigmap; + +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowStatus; +import io.serverlessworkflow.impl.marshaller.WorkflowBufferFactory; +import io.serverlessworkflow.impl.marshaller.WorkflowInputBuffer; +import io.serverlessworkflow.impl.persistence.PersistenceTaskInfo; +import java.io.ByteArrayInputStream; +import java.time.Instant; + +public class BytesBigMapPersistenceRestorer + extends BigMapPersistenceRestorer { + + private final WorkflowBufferFactory factory; + + protected BytesBigMapPersistenceRestorer( + BigMapPersistenceStore store, + WorkflowBufferFactory factory) { + super(store); + this.factory = factory; + } + + @Override + protected PersistenceTaskInfo unmarshallTaskInfo(byte[] taskData) { + try (WorkflowInputBuffer buffer = factory.input(new ByteArrayInputStream(taskData))) { + buffer.readByte(); // version byte not used at the moment + Instant date = buffer.readInstant(); + WorkflowModel model = (WorkflowModel) buffer.readObject(); + Boolean isEndNode = null; + String nextPosition = null; + isEndNode = buffer.readBoolean(); + boolean hasNext = buffer.readBoolean(); + if (hasNext) { + nextPosition = buffer.readString(); + } + return new PersistenceTaskInfo(date, model, isEndNode, nextPosition); + } + } + + @Override + protected PersistenceInstanceInfo unmarshallInstanceInfo(byte[] instanceData) { + try (WorkflowInputBuffer buffer = factory.input(new ByteArrayInputStream(instanceData))) { + buffer.readByte(); // version byte not used at the moment + return new PersistenceInstanceInfo(buffer.readInstant(), (WorkflowModel) buffer.readObject()); + } + } + + @Override + protected WorkflowModel unmarshallContext(byte[] contextData) { + try (WorkflowInputBuffer buffer = factory.input(new ByteArrayInputStream(contextData))) { + buffer.readByte(); // version byte not used at the moment + return (WorkflowModel) buffer.readObject(); + } + } + + @Override + protected WorkflowStatus unmarshallStatus(byte[] statusData) { + try (WorkflowInputBuffer buffer = factory.input(new ByteArrayInputStream(statusData))) { + buffer.readByte(); // version byte not used at the moment + return buffer.readEnum(WorkflowStatus.class); + } + } +} diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceStore.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceStore.java deleted file mode 100644 index a5c82579..00000000 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceStore.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2020-Present The Serverless Workflow Specification Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.serverlessworkflow.impl.persistence.bigmap; - -import io.serverlessworkflow.impl.TaskContextData; -import io.serverlessworkflow.impl.WorkflowContextData; -import io.serverlessworkflow.impl.WorkflowDefinition; -import io.serverlessworkflow.impl.WorkflowInstance; -import io.serverlessworkflow.impl.WorkflowInstanceData; -import io.serverlessworkflow.impl.WorkflowModel; -import io.serverlessworkflow.impl.WorkflowMutableInstance; -import io.serverlessworkflow.impl.WorkflowPosition; -import io.serverlessworkflow.impl.WorkflowStatus; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.time.Instant; - -public abstract class BytesBigMapPersistenceStore extends BigMapIdPersistenceStore { - - private final WorkflowBufferFactory factory; - - public BytesBigMapPersistenceStore(WorkflowBufferFactory factory) { - this.factory = factory; - } - - public BytesBigMapPersistenceStore() { - this(new DefaultBufferFactory()); - } - - @Override - protected WorkflowInstance unmarshall(WorkflowDefinition definition, byte[] bytes) { - - try (WorkflowInputBuffer reader = factory.input(new ByteArrayInputStream(bytes))) { - String id = reader.readString(); - WorkflowStatus status = reader.readEnum(WorkflowStatus.class); - WorkflowModel inputModel = readModel(bytes); - Instant startDate = reader.readInstant(); - WorkflowMutableInstance instance = - new WorkflowMutableInstance(definition, id, inputModel, status); - WorkflowModel context = readModel(bytes); - WorkflowPosition position = readPosition(bytes); - WorkflowModel model = readModel(bytes); - instance.restore(position, model, context, startDate); - return instance; - } - } - - private WorkflowPosition readPosition(byte[] bytes) { - // TODO read position - return null; - } - - private void writePosition(WorkflowPosition position) { - // TODO write position - } - - private WorkflowModel readModel(byte[] value) { - // TODO read model - return null; - } - - private void writeModel(WorkflowModel model) { - // TODO write model - } - - @Override - protected byte[] marshall(WorkflowContextData workflowContext, TaskContextData taskContext) { - - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - try (WorkflowOutputBuffer writer = factory.output(bytes)) { - WorkflowInstanceData instance = workflowContext.instanceData(); - writer.writeString(instance.id()); - writer.writeEnum(instance.status()); - writeModel(workflowContext.instanceData().input()); - writer.writeInstant(instance.startedAt()); - writeModel(workflowContext.context()); - writePosition(taskContext.position()); - writeModel(taskContext.input()); - } - return bytes.toByteArray(); - } -} diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceWriter.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceWriter.java new file mode 100644 index 00000000..825a2143 --- /dev/null +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceWriter.java @@ -0,0 +1,100 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.persistence.bigmap; + +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowContextData; +import io.serverlessworkflow.impl.WorkflowInstanceData; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowStatus; +import io.serverlessworkflow.impl.executors.AbstractTaskExecutor; +import io.serverlessworkflow.impl.executors.TaskExecutor; +import io.serverlessworkflow.impl.marshaller.WorkflowBufferFactory; +import io.serverlessworkflow.impl.marshaller.WorkflowOutputBuffer; +import io.serverlessworkflow.impl.persistence.bigmap.MarshallingUtils.TaskStatus; +import java.io.ByteArrayOutputStream; + +public class BytesBigMapPersistenceWriter + extends BigMapIdPersistenceWriter { + + private final WorkflowBufferFactory factory; + + public BytesBigMapPersistenceWriter( + BigMapPersistenceStore store, + WorkflowBufferFactory factory) { + super(store); + this.factory = factory; + } + + @Override + protected byte[] marshallTaskCompleted(WorkflowContextData contextData, TaskContext taskContext) { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + try (WorkflowOutputBuffer writer = factory.output(bytes)) { + writer.writeByte(MarshallingUtils.VERSION_0); + writer.writeInstant(taskContext.completedAt()); + writeModel(writer, taskContext.output()); + boolean isEndNode = taskContext.transition().isEndNode(); + writer.writeBoolean(isEndNode); + TaskExecutor next = taskContext.transition().next(); + if (next == null) { + writer.writeBoolean(false); + } else { + writer.writeBoolean(true); + writer.writeString(((AbstractTaskExecutor) next).position().jsonPointer()); + } + } + + return bytes.toByteArray(); + } + + protected void writeTaskStatus(WorkflowOutputBuffer buffer, TaskStatus taskStatus) { + buffer.writeByte((byte) taskStatus.ordinal()); + } + + @Override + protected byte[] marshallStatus(WorkflowStatus status) { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + try (WorkflowOutputBuffer writer = factory.output(bytes)) { + writer.writeByte(MarshallingUtils.VERSION_0); + writer.writeEnum(status); + } + return bytes.toByteArray(); + } + + @Override + protected byte[] marshallInstance(WorkflowInstanceData instance) { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + try (WorkflowOutputBuffer writer = factory.output(bytes)) { + writer.writeByte(MarshallingUtils.VERSION_0); + writeModel(writer, instance.input()); + writer.writeInstant(instance.startedAt()); + } + return bytes.toByteArray(); + } + + @Override + protected byte[] marshallContext(WorkflowContextData workflowContext) { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + try (WorkflowOutputBuffer writer = factory.output(bytes)) { + writeModel(writer, workflowContext.context()); + } + return bytes.toByteArray(); + } + + protected void writeModel(WorkflowOutputBuffer writer, WorkflowModel model) { + writer.writeObject(model); + } +} diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/MarshallingUtils.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/MarshallingUtils.java new file mode 100644 index 00000000..87006938 --- /dev/null +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/MarshallingUtils.java @@ -0,0 +1,28 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence.bigmap; + +class MarshallingUtils { + + private MarshallingUtils() {} + + public static final byte VERSION_0 = 0; + + public enum TaskStatus { + STARTED, + COMPLETED + } +} diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/PersistenceInstanceInfo.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/PersistenceInstanceInfo.java new file mode 100644 index 00000000..b4582986 --- /dev/null +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/PersistenceInstanceInfo.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.persistence.bigmap; + +import io.serverlessworkflow.impl.WorkflowModel; +import java.time.Instant; + +public record PersistenceInstanceInfo(Instant startedAt, WorkflowModel input) {} diff --git a/impl/persistence/jackson-marshaller/pom.xml b/impl/persistence/jackson-marshaller/pom.xml new file mode 100644 index 00000000..322a557c --- /dev/null +++ b/impl/persistence/jackson-marshaller/pom.xml @@ -0,0 +1,20 @@ + + 4.0.0 + + io.serverlessworkflow + serverlessworkflow-persistence + 8.0.0-SNAPSHOT + + serverlessworkflow-persistence-jackson-marshaller + Serverless Workflow :: Impl :: Persistence:: Marshaller:: Jackson + + + io.serverlessworkflow + serverlessworkflow-persistence-api + + + io.serverlessworkflow + serverlessworkflow-impl-jackson + + + \ No newline at end of file diff --git a/impl/persistence/jackson-marshaller/src/main/java/io/serverlessworkflow/impl/marshaller/jackson/JacksonModelMarshaller.java b/impl/persistence/jackson-marshaller/src/main/java/io/serverlessworkflow/impl/marshaller/jackson/JacksonModelMarshaller.java new file mode 100644 index 00000000..3e690988 --- /dev/null +++ b/impl/persistence/jackson-marshaller/src/main/java/io/serverlessworkflow/impl/marshaller/jackson/JacksonModelMarshaller.java @@ -0,0 +1,40 @@ +package io.serverlessworkflow.impl.marshaller.jackson; + +import java.io.IOException; +import java.io.UncheckedIOException; + +import com.fasterxml.jackson.core.JsonProcessingException; + +import io.serverlessworkflow.impl.expressions.jq.JacksonModel; +import io.serverlessworkflow.impl.jackson.JsonUtils; +import io.serverlessworkflow.impl.marshaller.CustomObjectMarshaller; +import io.serverlessworkflow.impl.marshaller.WorkflowInputBuffer; +import io.serverlessworkflow.impl.marshaller.WorkflowOutputBuffer; + +public class JacksonModelMarshaller implements CustomObjectMarshaller { + + @Override + public void write(WorkflowOutputBuffer buffer, JacksonModel object) { + try { + buffer.writeBytes(JsonUtils.mapper().writeValueAsBytes(object)); + } catch (JsonProcessingException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public JacksonModel read(WorkflowInputBuffer buffer) { + try { + return JsonUtils.mapper().readValue(buffer.readBytes(), JacksonModel.class); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + } + + @Override + public Class getObjectClass() { + return JacksonModel.class; + } + +} diff --git a/impl/persistence/mvstore/pom.xml b/impl/persistence/mvstore/pom.xml index 94892b91..1c9219e2 100644 --- a/impl/persistence/mvstore/pom.xml +++ b/impl/persistence/mvstore/pom.xml @@ -6,7 +6,7 @@ 8.0.0-SNAPSHOT serverlessworkflow-persistence-mvstore - Serverless Workflow :: Impl :: Pesistence:: MVStore + Serverless Workflow :: Impl :: Persistence:: MVStore io.serverlessworkflow diff --git a/impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStorePersistenceStore.java b/impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStorePersistenceStore.java index 35ce17fd..1d89a3ce 100644 --- a/impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStorePersistenceStore.java +++ b/impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStorePersistenceStore.java @@ -18,11 +18,12 @@ import io.serverlessworkflow.api.types.Document; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.WorkflowDefinitionData; -import io.serverlessworkflow.impl.persistence.bigmap.BytesBigMapPersistenceStore; +import io.serverlessworkflow.impl.persistence.bigmap.BigMapPersistenceStore; import java.util.Map; import org.h2.mvstore.MVStore; -public class MVStorePersistenceStore extends BytesBigMapPersistenceStore { +public class MVStorePersistenceStore + implements BigMapPersistenceStore { private final MVStore mvStore; protected static final String ID_SEPARATOR = "-"; @@ -36,15 +37,44 @@ protected static String identifier(Workflow workflow, String sep) { return document.getNamespace() + sep + document.getName() + sep + document.getVersion(); } - @Override - protected Map instances(WorkflowDefinitionData definition) { - return mvStore.openMap(identifier(definition.workflow(), ID_SEPARATOR)); - } - @Override public void close() { if (!mvStore.isClosed()) { mvStore.close(); } } + + @Override + public Map instanceData(WorkflowDefinitionData workflowContext) { + return openMap(workflowContext, "instances"); + } + + @Override + public Map tasks(String instanceId) { + return mvStore.openMap(mapTaskName(instanceId)); + } + + @Override + public Map status(WorkflowDefinitionData workflowContext) { + return openMap(workflowContext, "status"); + } + + @Override + public Map context(WorkflowDefinitionData workflowContext) { + return openMap(workflowContext, "context"); + } + + private Map openMap(WorkflowDefinitionData workflowDefinition, String suffix) { + return mvStore.openMap( + identifier(workflowDefinition.workflow(), ID_SEPARATOR) + ID_SEPARATOR + suffix); + } + + private String mapTaskName(String instanceId) { + return instanceId + ID_SEPARATOR + "tasks"; + } + + @Override + public void cleanupTasks(String instanceId) { + mvStore.removeMap(mapTaskName(instanceId)); + } } From edd3c3c80d67df504075a040bf87f104328f8979 Mon Sep 17 00:00:00 2001 From: fjtirado Date: Mon, 6 Oct 2025 14:00:30 +0200 Subject: [PATCH 3/7] [Fix #782] Fixes after manual testing Signed-off-by: fjtirado --- .../impl/WorkflowApplication.java | 15 ++-- .../impl/WorkflowMutableInstance.java | 4 +- .../impl/WorkflowUtils.java | 12 ++++ .../impl/executors/AbstractTaskExecutor.java | 2 +- .../lifecycle/WorkflowExecutionListener.java | 5 +- .../impl/expressions/jq/JacksonModel.java | 2 + .../jq/JacksonModelDeserializer.java | 37 ++++++++++ .../impl/marshaller/AbstractInputBuffer.java | 5 +- .../impl/marshaller/AbstractOutputBuffer.java | 18 ++--- .../impl/marshaller/DefaultBufferFactory.java | 8 +++ .../impl/marshaller/Type.java | 3 +- .../impl/persistence/PersistenceTaskInfo.java | 6 +- .../persistence/PersistenceWorkflowInfo.java | 1 - .../WorkflowPersistenceInstance.java | 8 +-- .../WorkflowPersistenceListener.java | 16 ++++- .../WorkflowPersistenceRestorer.java | 3 + .../WorkflowPersistenceWriter.java | 3 + .../bigmap/BigMapIdPersistenceWriter.java | 6 +- .../bigmap/BigMapPersistenceRestorer.java | 26 +++---- .../bigmap/BigMapPersistenceStore.java | 4 +- .../bigmap/BigMapPersistenceWriter.java | 18 +---- .../bigmap/BytesBigMapApplicationBuilder.java | 14 ++-- .../BytesBigMapPersistenceRestorer.java | 18 ++--- .../bigmap/BytesBigMapPersistenceWriter.java | 17 ++--- .../jackson/JacksonModelMarshaller.java | 67 ++++++++++-------- ...low.impl.marshaller.CustomObjectMarshaller | 1 + impl/persistence/mvstore/pom.xml | 41 +++++++++-- .../mvstore/MVStorePersistenceStore.java | 7 +- .../tests/persistence/DBGenerator.java | 38 ++++++++++ .../persistence/MvStorePersistenceTest.java | 57 +++++++++++++++ .../src/test/resources/listen-to-any.yaml | 10 +++ impl/persistence/mvstore/test.db | Bin 0 -> 16384 bytes impl/persistence/pom.xml | 1 + impl/pom.xml | 5 ++ 34 files changed, 332 insertions(+), 146 deletions(-) create mode 100644 impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModelDeserializer.java create mode 100644 impl/persistence/jackson-marshaller/src/main/resources/META-INF/services/io.serverlessworkflow.impl.marshaller.CustomObjectMarshaller create mode 100644 impl/persistence/mvstore/src/test/java/io/serverlessworkflow/tests/persistence/DBGenerator.java create mode 100644 impl/persistence/mvstore/src/test/java/io/serverlessworkflow/tests/persistence/MvStorePersistenceTest.java create mode 100644 impl/persistence/mvstore/src/test/resources/listen-to-any.yaml create mode 100644 impl/persistence/mvstore/test.db 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 8e25e651..9e4cc008 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java @@ -15,6 +15,8 @@ */ package io.serverlessworkflow.impl; +import static io.serverlessworkflow.impl.WorkflowUtils.safeClose; + import io.serverlessworkflow.api.types.SchemaInline; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.events.EventConsumer; @@ -39,13 +41,9 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.stream.Collectors; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class WorkflowApplication implements AutoCloseable { - private static final Logger logger = LoggerFactory.getLogger(WorkflowApplication.class); - private final TaskExecutorFactory taskFactory; private final ExpressionFactory exprFactory; private final ResourceLoaderFactory resourceLoaderFactory; @@ -271,14 +269,11 @@ public void close() { safeClose(definition); } definitions.clear(); - } - private void safeClose(AutoCloseable closeable) { - try { - closeable.close(); - } catch (Exception ex) { - logger.warn("Error closing resource {}", closeable.getClass().getName(), ex); + for (WorkflowExecutionListener listener : listeners) { + safeClose(listener); } + listeners.clear(); } public WorkflowPositionFactory positionFactory() { diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowMutableInstance.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowMutableInstance.java index 0cea457b..51536aa8 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowMutableInstance.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowMutableInstance.java @@ -50,7 +50,7 @@ public class WorkflowMutableInstance implements WorkflowInstance { private Lock statusLock = new ReentrantLock(); private Map, TaskContext> suspended; - public WorkflowMutableInstance(WorkflowDefinition definition, String id, WorkflowModel input) { + protected WorkflowMutableInstance(WorkflowDefinition definition, String id, WorkflowModel input) { this.id = id; this.input = input; this.status = new AtomicReference<>(WorkflowStatus.PENDING); @@ -265,5 +265,5 @@ public boolean cancel() { } } - public void restoreContext(WorkflowDefinition definition, TaskContext context) {} + public void restoreContext(WorkflowContext workflow, TaskContext context) {} } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java index db056676..b48945af 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java @@ -28,11 +28,15 @@ import java.net.URI; import java.util.Map; import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class WorkflowUtils { private WorkflowUtils() {} + private static final Logger logger = LoggerFactory.getLogger(WorkflowUtils.class); + public static Optional getSchemaValidator( SchemaValidatorFactory validatorFactory, ResourceLoader resourceLoader, SchemaUnion schema) { if (schema != null) { @@ -138,4 +142,12 @@ public static String toString(UriTemplate template) { URI uri = template.getLiteralUri(); return uri != null ? uri.toString() : template.getLiteralUriTemplate(); } + + public static void safeClose(AutoCloseable closeable) { + try { + closeable.close(); + } catch (Exception ex) { + logger.warn("Error closing resource {}", closeable.getClass().getName(), ex); + } + } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java index e0242f32..4193981d 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java @@ -192,7 +192,7 @@ private CompletableFuture executeNext( public CompletableFuture apply( WorkflowContext workflowContext, Optional parentContext, WorkflowModel input) { TaskContext taskContext = new TaskContext(input, position, parentContext, taskName, task); - workflowContext.instance().restoreContext(workflowContext.definition(), taskContext); + workflowContext.instance().restoreContext(workflowContext, taskContext); CompletableFuture completable = CompletableFuture.completedFuture(taskContext); if (taskContext.isCompleted() && !TaskExecutorHelper.isActive(workflowContext)) { return completable; diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/lifecycle/WorkflowExecutionListener.java b/impl/core/src/main/java/io/serverlessworkflow/impl/lifecycle/WorkflowExecutionListener.java index 8d89fac7..e88e8cbd 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/lifecycle/WorkflowExecutionListener.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/lifecycle/WorkflowExecutionListener.java @@ -15,7 +15,7 @@ */ package io.serverlessworkflow.impl.lifecycle; -public interface WorkflowExecutionListener { +public interface WorkflowExecutionListener extends AutoCloseable { default void onWorkflowStarted(WorkflowStartedEvent ev) {} @@ -42,4 +42,7 @@ default void onTaskSuspended(TaskSuspendedEvent ev) {} default void onTaskResumed(TaskResumedEvent ev) {} default void onTaskRetried(TaskRetriedEvent ev) {} + + @Override + default void close() {} } diff --git a/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModel.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModel.java index b2054abf..c3a61893 100644 --- a/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModel.java +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModel.java @@ -16,6 +16,7 @@ package io.serverlessworkflow.impl.expressions.jq; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.BooleanNode; @@ -29,6 +30,7 @@ import java.util.Optional; @JsonSerialize(using = JacksonModelSerializer.class) +@JsonDeserialize(using = JacksonModelDeserializer.class) public class JacksonModel implements WorkflowModel { protected JsonNode node; diff --git a/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModelDeserializer.java b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModelDeserializer.java new file mode 100644 index 00000000..73d10bc1 --- /dev/null +++ b/impl/jackson/src/main/java/io/serverlessworkflow/impl/expressions/jq/JacksonModelDeserializer.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.expressions.jq; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import java.io.IOException; + +public class JacksonModelDeserializer extends StdDeserializer { + + private static final long serialVersionUID = 1L; + + protected JacksonModelDeserializer() { + super(JacksonModel.class); + } + + @Override + public JacksonModel deserialize(JsonParser p, DeserializationContext ctxt) + throws IOException, JacksonException { + return new JacksonModel(p.readValueAsTree()); + } +} diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/AbstractInputBuffer.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/AbstractInputBuffer.java index 746e7a67..b7971717 100644 --- a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/AbstractInputBuffer.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/AbstractInputBuffer.java @@ -108,8 +108,11 @@ public Object readObject() { case INSTANT: return readInstant(); - default: + case CUSTOM: return readCustomObject(); + + default: + throw new IllegalStateException("Unsupported type " + type); } } diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/AbstractOutputBuffer.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/AbstractOutputBuffer.java index 3b368b53..fb002800 100644 --- a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/AbstractOutputBuffer.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/AbstractOutputBuffer.java @@ -99,6 +99,7 @@ public WorkflowOutputBuffer writeObject(Object object) { writeType(Type.BYTES); writeBytes(bytes); } else { + writeType(Type.CUSTOM); writeCustomObject(object); } return this; @@ -109,15 +110,14 @@ protected void writeClass(Class objectClass) { } protected void writeCustomObject(Object object) { - customMarshallers.stream() - .filter(m -> m.getObjectClass().isAssignableFrom(object.getClass())) - .findFirst() - .ifPresentOrElse( - m -> { - writeClass(m.getObjectClass()); - m.write(this, m.getObjectClass().cast(object)); - }, - () -> new IllegalArgumentException("Unsupported type " + object.getClass())); + CustomObjectMarshaller marshaller = + customMarshallers.stream() + .filter(m -> m.getObjectClass().isAssignableFrom(object.getClass())) + .findFirst() + .orElseThrow( + () -> new IllegalArgumentException("Unsupported type " + object.getClass())); + writeClass(marshaller.getObjectClass()); + marshaller.write(this, marshaller.getObjectClass().cast(object)); } protected void writeType(Type type) { diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/DefaultBufferFactory.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/DefaultBufferFactory.java index 54b27cfd..5d8a4b4d 100644 --- a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/DefaultBufferFactory.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/DefaultBufferFactory.java @@ -18,11 +18,19 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.Collection; +import java.util.ServiceLoader; public class DefaultBufferFactory implements WorkflowBufferFactory { private final Collection marshallers; + public static DefaultBufferFactory factory() { + return new DefaultBufferFactory( + ServiceLoader.load(CustomObjectMarshaller.class).stream() + .map(ServiceLoader.Provider::get) + .toList()); + } + public DefaultBufferFactory(Collection marshallers) { this.marshallers = marshallers; } diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/Type.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/Type.java index 9183c52c..cf52b7ab 100644 --- a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/Type.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/Type.java @@ -28,5 +28,6 @@ enum Type { INSTANT, MAP, COLLECTION, - NULL + NULL, + CUSTOM } diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceTaskInfo.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceTaskInfo.java index 2a127c9a..2e6c688b 100644 --- a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceTaskInfo.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceTaskInfo.java @@ -19,4 +19,8 @@ import java.time.Instant; public record PersistenceTaskInfo( - Instant instant, WorkflowModel model, Boolean isEndNode, String nextPosition) {} + Instant instant, + WorkflowModel model, + WorkflowModel context, + Boolean isEndNode, + String nextPosition) {} diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceWorkflowInfo.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceWorkflowInfo.java index 3bef5bf4..0c5d27bf 100644 --- a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceWorkflowInfo.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceWorkflowInfo.java @@ -24,6 +24,5 @@ public record PersistenceWorkflowInfo( String id, Instant startedAt, WorkflowModel input, - WorkflowModel context, WorkflowStatus status, Map tasks) {} diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceInstance.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceInstance.java index 8f3921ea..d266bb32 100644 --- a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceInstance.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceInstance.java @@ -16,6 +16,7 @@ package io.serverlessworkflow.impl.persistence; import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowContext; import io.serverlessworkflow.impl.WorkflowDefinition; import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowMutableInstance; @@ -36,13 +37,11 @@ public CompletableFuture start() { return startExecution( () -> { startedAt = info.startedAt(); - status.set(info.status()); - workflowContext.context(info.context()); }); } @Override - public void restoreContext(WorkflowDefinition definition, TaskContext context) { + public void restoreContext(WorkflowContext workflow, TaskContext context) { PersistenceTaskInfo taskInfo = info.tasks().get(context.position().jsonPointer()); if (taskInfo != null) { context.output(taskInfo.model()); @@ -51,8 +50,9 @@ public void restoreContext(WorkflowDefinition definition, TaskContext context) { new TransitionInfo( taskInfo.nextPosition() == null ? null - : definition.taskExecutor(taskInfo.nextPosition()), + : workflow.definition().taskExecutor(taskInfo.nextPosition()), taskInfo.isEndNode())); + workflow.context(taskInfo.context()); } } } diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceListener.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceListener.java index 6c1a88e0..60d8b723 100644 --- a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceListener.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceListener.java @@ -15,9 +15,12 @@ */ package io.serverlessworkflow.impl.persistence; +import static io.serverlessworkflow.impl.WorkflowUtils.safeClose; + import io.serverlessworkflow.impl.lifecycle.TaskCompletedEvent; import io.serverlessworkflow.impl.lifecycle.TaskStartedEvent; import io.serverlessworkflow.impl.lifecycle.WorkflowCancelledEvent; +import io.serverlessworkflow.impl.lifecycle.WorkflowCompletedEvent; import io.serverlessworkflow.impl.lifecycle.WorkflowExecutionListener; import io.serverlessworkflow.impl.lifecycle.WorkflowFailedEvent; import io.serverlessworkflow.impl.lifecycle.WorkflowResumedEvent; @@ -28,8 +31,8 @@ public class WorkflowPersistenceListener implements WorkflowExecutionListener { private final WorkflowPersistenceWriter persistenceWriter; - public WorkflowPersistenceListener(WorkflowPersistenceWriter persistenceStore) { - this.persistenceWriter = persistenceStore; + public WorkflowPersistenceListener(WorkflowPersistenceWriter persistenceWriter) { + this.persistenceWriter = persistenceWriter; } @Override @@ -57,6 +60,11 @@ public void onWorkflowResumed(WorkflowResumedEvent ev) { persistenceWriter.resumed(ev.workflowContext()); } + @Override + public void onWorkflowCompleted(WorkflowCompletedEvent ev) { + persistenceWriter.completed(ev.workflowContext()); + } + @Override public void onTaskStarted(TaskStartedEvent ev) { persistenceWriter.taskStarted(ev.workflowContext(), ev.taskContext()); @@ -66,4 +74,8 @@ public void onTaskStarted(TaskStartedEvent ev) { public void onTaskCompleted(TaskCompletedEvent ev) { persistenceWriter.taskCompleted(ev.workflowContext(), ev.taskContext()); } + + public void close() { + safeClose(persistenceWriter); + } } diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceRestorer.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceRestorer.java index 1ee81f39..b1aa0093 100644 --- a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceRestorer.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceRestorer.java @@ -28,4 +28,7 @@ Map restore( WorkflowDefinition definition, Collection instanceIds); Optional restore(WorkflowDefinition definition, String instanceId); + + @Override + default void close() {} } diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceWriter.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceWriter.java index aceef40d..57ea620c 100644 --- a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceWriter.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceWriter.java @@ -35,4 +35,7 @@ public interface WorkflowPersistenceWriter extends AutoCloseable { void taskStarted(WorkflowContextData workflowContext, TaskContextData taskContext); void taskCompleted(WorkflowContextData workflowContext, TaskContextData taskContext); + + @Override + default void close() {} } diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapIdPersistenceWriter.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapIdPersistenceWriter.java index 46cc226e..55cd1fcd 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapIdPersistenceWriter.java +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapIdPersistenceWriter.java @@ -17,10 +17,10 @@ import io.serverlessworkflow.impl.WorkflowContextData; -public abstract class BigMapIdPersistenceWriter - extends BigMapPersistenceWriter { +public abstract class BigMapIdPersistenceWriter + extends BigMapPersistenceWriter { - protected BigMapIdPersistenceWriter(BigMapPersistenceStore store) { + protected BigMapIdPersistenceWriter(BigMapPersistenceStore store) { super(store); } diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceRestorer.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceRestorer.java index 332a0add..c1e0f731 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceRestorer.java +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceRestorer.java @@ -17,7 +17,6 @@ import io.serverlessworkflow.impl.WorkflowDefinition; import io.serverlessworkflow.impl.WorkflowInstance; -import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowStatus; import io.serverlessworkflow.impl.persistence.PersistenceTaskInfo; import io.serverlessworkflow.impl.persistence.PersistenceWorkflowInfo; @@ -29,11 +28,11 @@ import java.util.Optional; import java.util.stream.Collectors; -public abstract class BigMapPersistenceRestorer implements WorkflowPersistenceRestorer { +public abstract class BigMapPersistenceRestorer implements WorkflowPersistenceRestorer { - private final BigMapPersistenceStore store; + private final BigMapPersistenceStore store; - protected BigMapPersistenceRestorer(BigMapPersistenceStore store) { + protected BigMapPersistenceRestorer(BigMapPersistenceStore store) { this.store = store; } @@ -41,7 +40,6 @@ protected BigMapPersistenceRestorer(BigMapPersistenceStore s public Map restoreAll(WorkflowDefinition definition) { Map instances = store.instanceData(definition); Map status = store.status(definition); - Map context = store.context(definition); return instances.entrySet().stream() .map( e -> @@ -50,8 +48,7 @@ public Map restoreAll(WorkflowDefinition definition) { e.getKey(), e.getValue(), store.tasks(e.getKey()), - status.get(e.getKey()), - context.get(e.getKey()))) + status.get(e.getKey()))) .collect(Collectors.toMap(WorkflowInstance::id, i -> i)); } @@ -75,8 +72,7 @@ public Optional restore(WorkflowDefinition definition, String instanceId, instances.get(instanceId), store.tasks(instanceId), - store.status(definition).get(instanceId), - store.context(definition).get(instanceId))); + store.status(definition).get(instanceId))); } public void close() {} @@ -86,29 +82,25 @@ protected WorkflowInstance restore( String instanceId, V instanceData, Map tasksData, - S status, - C context) { + S status) { return new WorkflowPersistenceInstance( - definition, readPersistenceInfo(instanceId, instanceData, tasksData, status, context)); + definition, readPersistenceInfo(instanceId, instanceData, tasksData, status)); } protected abstract PersistenceTaskInfo unmarshallTaskInfo(T taskData); protected abstract PersistenceInstanceInfo unmarshallInstanceInfo(V instanceData); - protected abstract WorkflowModel unmarshallContext(C contextData); - protected abstract WorkflowStatus unmarshallStatus(S statusData); protected PersistenceWorkflowInfo readPersistenceInfo( - String instanceId, V instanceData, Map tasksData, S status, C context) { + String instanceId, V instanceData, Map tasksData, S status) { PersistenceInstanceInfo instanceInfo = unmarshallInstanceInfo(instanceData); return new PersistenceWorkflowInfo( instanceId, instanceInfo.startedAt(), instanceInfo.input(), - unmarshallContext(context), - unmarshallStatus(status), + status == null ? null : unmarshallStatus(status), tasksData.entrySet().stream() .collect( Collectors.toMap(Entry::getKey, entry -> unmarshallTaskInfo(entry.getValue())))); diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceStore.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceStore.java index 689e7cd7..a24781f1 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceStore.java +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceStore.java @@ -18,12 +18,10 @@ import io.serverlessworkflow.impl.WorkflowDefinitionData; import java.util.Map; -public interface BigMapPersistenceStore extends AutoCloseable { +public interface BigMapPersistenceStore extends AutoCloseable { Map instanceData(WorkflowDefinitionData definition); - Map context(WorkflowDefinitionData workflowContext); - Map status(WorkflowDefinitionData workflowContext); Map tasks(K instanceId); diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceWriter.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceWriter.java index ec8e2d73..ee986084 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceWriter.java +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceWriter.java @@ -22,11 +22,11 @@ import io.serverlessworkflow.impl.WorkflowStatus; import io.serverlessworkflow.impl.persistence.WorkflowPersistenceWriter; -public abstract class BigMapPersistenceWriter implements WorkflowPersistenceWriter { +public abstract class BigMapPersistenceWriter implements WorkflowPersistenceWriter { - private BigMapPersistenceStore store; + private BigMapPersistenceStore store; - protected BigMapPersistenceWriter(BigMapPersistenceStore store) { + protected BigMapPersistenceWriter(BigMapPersistenceStore store) { this.store = store; } @@ -63,7 +63,6 @@ public void taskCompleted(WorkflowContextData workflowContext, TaskContextData t .put( taskContext.position().jsonPointer(), marshallTaskCompleted(workflowContext, (TaskContext) taskContext)); - store.context(workflowContext.definition()).put(key, marshallContext(workflowContext)); } @Override @@ -83,7 +82,6 @@ public void resumed(WorkflowContextData workflowContext) { protected void removeProcessInstance(WorkflowContextData workflowContext) { K key = key(workflowContext); store.instanceData(workflowContext.definition()).remove(key); - store.context(workflowContext.definition()).remove(key); store.status(workflowContext.definition()).remove(key); store.cleanupTasks(key); } @@ -92,18 +90,8 @@ protected void removeProcessInstance(WorkflowContextData workflowContext) { protected abstract V marshallInstance(WorkflowInstanceData instance); - protected abstract C marshallContext(WorkflowContextData workflowContext); - protected abstract T marshallTaskCompleted( WorkflowContextData workflowContext, TaskContext taskContext); protected abstract S marshallStatus(WorkflowStatus status); - - public void close() { - try { - store.close(); - } catch (Exception e) { - // ignore exception - } - } } diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapApplicationBuilder.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapApplicationBuilder.java index 388382f0..e8281deb 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapApplicationBuilder.java +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapApplicationBuilder.java @@ -17,26 +17,24 @@ import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowApplication.Builder; -import io.serverlessworkflow.impl.marshaller.CustomObjectMarshaller; import io.serverlessworkflow.impl.marshaller.DefaultBufferFactory; import io.serverlessworkflow.impl.marshaller.WorkflowBufferFactory; import io.serverlessworkflow.impl.persistence.WorkflowPersistenceListener; -import java.util.ServiceLoader; public class BytesBigMapApplicationBuilder { public static BytesBigMapApplicationBuilder builder( WorkflowApplication.Builder builder, - BigMapPersistenceStore store) { + BigMapPersistenceStore store) { return new BytesBigMapApplicationBuilder(builder, store); } - private final BigMapPersistenceStore store; + private final BigMapPersistenceStore store; private final WorkflowApplication.Builder appBuilder; private WorkflowBufferFactory factory; protected BytesBigMapApplicationBuilder( - Builder appBuilder, BigMapPersistenceStore store) { + Builder appBuilder, BigMapPersistenceStore store) { this.appBuilder = appBuilder; this.store = store; } @@ -48,11 +46,7 @@ public BytesBigMapApplicationBuilder withFactory(WorkflowBufferFactory factory) public WorkflowApplication build() { if (factory == null) { - factory = - new DefaultBufferFactory( - ServiceLoader.load(CustomObjectMarshaller.class).stream() - .map(ServiceLoader.Provider::get) - .toList()); + factory = DefaultBufferFactory.factory(); } appBuilder.withListener( new WorkflowPersistenceListener(new BytesBigMapPersistenceWriter(store, factory))); diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceRestorer.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceRestorer.java index 805bf9ba..5a552528 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceRestorer.java +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceRestorer.java @@ -24,13 +24,12 @@ import java.time.Instant; public class BytesBigMapPersistenceRestorer - extends BigMapPersistenceRestorer { + extends BigMapPersistenceRestorer { private final WorkflowBufferFactory factory; - protected BytesBigMapPersistenceRestorer( - BigMapPersistenceStore store, - WorkflowBufferFactory factory) { + public BytesBigMapPersistenceRestorer( + BigMapPersistenceStore store, WorkflowBufferFactory factory) { super(store); this.factory = factory; } @@ -41,6 +40,7 @@ protected PersistenceTaskInfo unmarshallTaskInfo(byte[] taskData) { buffer.readByte(); // version byte not used at the moment Instant date = buffer.readInstant(); WorkflowModel model = (WorkflowModel) buffer.readObject(); + WorkflowModel context = (WorkflowModel) buffer.readObject(); Boolean isEndNode = null; String nextPosition = null; isEndNode = buffer.readBoolean(); @@ -48,7 +48,7 @@ protected PersistenceTaskInfo unmarshallTaskInfo(byte[] taskData) { if (hasNext) { nextPosition = buffer.readString(); } - return new PersistenceTaskInfo(date, model, isEndNode, nextPosition); + return new PersistenceTaskInfo(date, model, context, isEndNode, nextPosition); } } @@ -60,14 +60,6 @@ protected PersistenceInstanceInfo unmarshallInstanceInfo(byte[] instanceData) { } } - @Override - protected WorkflowModel unmarshallContext(byte[] contextData) { - try (WorkflowInputBuffer buffer = factory.input(new ByteArrayInputStream(contextData))) { - buffer.readByte(); // version byte not used at the moment - return (WorkflowModel) buffer.readObject(); - } - } - @Override protected WorkflowStatus unmarshallStatus(byte[] statusData) { try (WorkflowInputBuffer buffer = factory.input(new ByteArrayInputStream(statusData))) { diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceWriter.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceWriter.java index 825a2143..419b7800 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceWriter.java +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceWriter.java @@ -28,13 +28,12 @@ import java.io.ByteArrayOutputStream; public class BytesBigMapPersistenceWriter - extends BigMapIdPersistenceWriter { + extends BigMapIdPersistenceWriter { private final WorkflowBufferFactory factory; public BytesBigMapPersistenceWriter( - BigMapPersistenceStore store, - WorkflowBufferFactory factory) { + BigMapPersistenceStore store, WorkflowBufferFactory factory) { super(store); this.factory = factory; } @@ -46,6 +45,7 @@ protected byte[] marshallTaskCompleted(WorkflowContextData contextData, TaskCont writer.writeByte(MarshallingUtils.VERSION_0); writer.writeInstant(taskContext.completedAt()); writeModel(writer, taskContext.output()); + writeModel(writer, contextData.context()); boolean isEndNode = taskContext.transition().isEndNode(); writer.writeBoolean(isEndNode); TaskExecutor next = taskContext.transition().next(); @@ -79,17 +79,8 @@ protected byte[] marshallInstance(WorkflowInstanceData instance) { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); try (WorkflowOutputBuffer writer = factory.output(bytes)) { writer.writeByte(MarshallingUtils.VERSION_0); - writeModel(writer, instance.input()); writer.writeInstant(instance.startedAt()); - } - return bytes.toByteArray(); - } - - @Override - protected byte[] marshallContext(WorkflowContextData workflowContext) { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - try (WorkflowOutputBuffer writer = factory.output(bytes)) { - writeModel(writer, workflowContext.context()); + writeModel(writer, instance.input()); } return bytes.toByteArray(); } diff --git a/impl/persistence/jackson-marshaller/src/main/java/io/serverlessworkflow/impl/marshaller/jackson/JacksonModelMarshaller.java b/impl/persistence/jackson-marshaller/src/main/java/io/serverlessworkflow/impl/marshaller/jackson/JacksonModelMarshaller.java index 3e690988..ee0933ff 100644 --- a/impl/persistence/jackson-marshaller/src/main/java/io/serverlessworkflow/impl/marshaller/jackson/JacksonModelMarshaller.java +++ b/impl/persistence/jackson-marshaller/src/main/java/io/serverlessworkflow/impl/marshaller/jackson/JacksonModelMarshaller.java @@ -1,40 +1,51 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package io.serverlessworkflow.impl.marshaller.jackson; -import java.io.IOException; -import java.io.UncheckedIOException; - import com.fasterxml.jackson.core.JsonProcessingException; - import io.serverlessworkflow.impl.expressions.jq.JacksonModel; import io.serverlessworkflow.impl.jackson.JsonUtils; import io.serverlessworkflow.impl.marshaller.CustomObjectMarshaller; import io.serverlessworkflow.impl.marshaller.WorkflowInputBuffer; import io.serverlessworkflow.impl.marshaller.WorkflowOutputBuffer; +import java.io.IOException; +import java.io.UncheckedIOException; public class JacksonModelMarshaller implements CustomObjectMarshaller { - @Override - public void write(WorkflowOutputBuffer buffer, JacksonModel object) { - try { - buffer.writeBytes(JsonUtils.mapper().writeValueAsBytes(object)); - } catch (JsonProcessingException e) { - throw new UncheckedIOException(e); - } - } - - @Override - public JacksonModel read(WorkflowInputBuffer buffer) { - try { - return JsonUtils.mapper().readValue(buffer.readBytes(), JacksonModel.class); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - - } - - @Override - public Class getObjectClass() { - return JacksonModel.class; - } - + @Override + public void write(WorkflowOutputBuffer buffer, JacksonModel object) { + try { + buffer.writeBytes(JsonUtils.mapper().writeValueAsBytes(object)); + } catch (JsonProcessingException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public JacksonModel read(WorkflowInputBuffer buffer) { + try { + return JsonUtils.mapper().readValue(buffer.readBytes(), JacksonModel.class); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public Class getObjectClass() { + return JacksonModel.class; + } } diff --git a/impl/persistence/jackson-marshaller/src/main/resources/META-INF/services/io.serverlessworkflow.impl.marshaller.CustomObjectMarshaller b/impl/persistence/jackson-marshaller/src/main/resources/META-INF/services/io.serverlessworkflow.impl.marshaller.CustomObjectMarshaller new file mode 100644 index 00000000..81b32636 --- /dev/null +++ b/impl/persistence/jackson-marshaller/src/main/resources/META-INF/services/io.serverlessworkflow.impl.marshaller.CustomObjectMarshaller @@ -0,0 +1 @@ +io.serverlessworkflow.impl.marshaller.jackson.JacksonModelMarshaller \ No newline at end of file diff --git a/impl/persistence/mvstore/pom.xml b/impl/persistence/mvstore/pom.xml index 1c9219e2..469be107 100644 --- a/impl/persistence/mvstore/pom.xml +++ b/impl/persistence/mvstore/pom.xml @@ -5,17 +5,48 @@ serverlessworkflow-persistence 8.0.0-SNAPSHOT + + 1.4.199 + serverlessworkflow-persistence-mvstore Serverless Workflow :: Impl :: Persistence:: MVStore - - io.serverlessworkflow - serverlessworkflow-persistence-big-map - com.h2database h2-mvstore - 1.4.199 + ${version.com.h2database} + + + io.serverlessworkflow + serverlessworkflow-persistence-big-map + + + io.serverlessworkflow + serverlessworkflow-api + test + + + io.serverlessworkflow + serverlessworkflow-impl-jackson + test + + + ch.qos.logback + logback-classic + test + + + io.serverlessworkflow + serverlessworkflow-persistence-jackson-marshaller + test + + + org.junit.jupiter + junit-jupiter-engine + + + org.assertj + assertj-core \ No newline at end of file diff --git a/impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStorePersistenceStore.java b/impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStorePersistenceStore.java index 1d89a3ce..5baa4341 100644 --- a/impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStorePersistenceStore.java +++ b/impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStorePersistenceStore.java @@ -23,7 +23,7 @@ import org.h2.mvstore.MVStore; public class MVStorePersistenceStore - implements BigMapPersistenceStore { + implements BigMapPersistenceStore { private final MVStore mvStore; protected static final String ID_SEPARATOR = "-"; @@ -59,11 +59,6 @@ public Map status(WorkflowDefinitionData workflowContext) { return openMap(workflowContext, "status"); } - @Override - public Map context(WorkflowDefinitionData workflowContext) { - return openMap(workflowContext, "context"); - } - private Map openMap(WorkflowDefinitionData workflowDefinition, String suffix) { return mvStore.openMap( identifier(workflowDefinition.workflow(), ID_SEPARATOR) + ID_SEPARATOR + suffix); diff --git a/impl/persistence/mvstore/src/test/java/io/serverlessworkflow/tests/persistence/DBGenerator.java b/impl/persistence/mvstore/src/test/java/io/serverlessworkflow/tests/persistence/DBGenerator.java new file mode 100644 index 00000000..56df139c --- /dev/null +++ b/impl/persistence/mvstore/src/test/java/io/serverlessworkflow/tests/persistence/DBGenerator.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.tests.persistence; + +import static io.serverlessworkflow.api.WorkflowReader.readWorkflowFromClasspath; + +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowDefinition; +import io.serverlessworkflow.impl.persistence.bigmap.BytesBigMapApplicationBuilder; +import io.serverlessworkflow.impl.persistence.mvstore.MVStorePersistenceStore; +import java.io.IOException; +import java.util.Map; + +public class DBGenerator { + + public static void main(String[] args) throws IOException { + try (MVStorePersistenceStore store = new MVStorePersistenceStore("test.db"); + WorkflowApplication application = + BytesBigMapApplicationBuilder.builder(WorkflowApplication.builder(), store).build()) { + WorkflowDefinition definition = + application.workflowDefinition(readWorkflowFromClasspath("listen-to-any.yaml")); + definition.instance(Map.of()).start(); + } + } +} diff --git a/impl/persistence/mvstore/src/test/java/io/serverlessworkflow/tests/persistence/MvStorePersistenceTest.java b/impl/persistence/mvstore/src/test/java/io/serverlessworkflow/tests/persistence/MvStorePersistenceTest.java new file mode 100644 index 00000000..7b4fa60d --- /dev/null +++ b/impl/persistence/mvstore/src/test/java/io/serverlessworkflow/tests/persistence/MvStorePersistenceTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.tests.persistence; + +import static io.serverlessworkflow.api.WorkflowReader.readWorkflowFromClasspath; +import static org.assertj.core.api.Assertions.assertThat; + +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowDefinition; +import io.serverlessworkflow.impl.WorkflowInstance; +import io.serverlessworkflow.impl.WorkflowStatus; +import io.serverlessworkflow.impl.marshaller.DefaultBufferFactory; +import io.serverlessworkflow.impl.marshaller.WorkflowBufferFactory; +import io.serverlessworkflow.impl.persistence.WorkflowPersistenceRestorer; +import io.serverlessworkflow.impl.persistence.bigmap.BytesBigMapApplicationBuilder; +import io.serverlessworkflow.impl.persistence.bigmap.BytesBigMapPersistenceRestorer; +import io.serverlessworkflow.impl.persistence.mvstore.MVStorePersistenceStore; +import java.io.IOException; +import java.util.Collection; +import org.junit.jupiter.api.Test; + +public class MvStorePersistenceTest { + + @Test + public void testRestoreWaitingInstance() throws IOException { + WorkflowBufferFactory bufferFactory = DefaultBufferFactory.factory(); + try (MVStorePersistenceStore store = new MVStorePersistenceStore("test.db"); + WorkflowApplication application = + BytesBigMapApplicationBuilder.builder(WorkflowApplication.builder(), store) + .withFactory(bufferFactory) + .build(); + WorkflowPersistenceRestorer restorer = + new BytesBigMapPersistenceRestorer(store, bufferFactory); ) { + WorkflowDefinition definition = + application.workflowDefinition(readWorkflowFromClasspath("listen-to-any.yaml")); + Collection instances = restorer.restoreAll(definition).values(); + assertThat(instances).hasSize(1); + instances.forEach(WorkflowInstance::start); + assertThat(instances) + .singleElement() + .satisfies((instance -> assertThat(instance.status()).isEqualTo(WorkflowStatus.WAITING))); + } + } +} diff --git a/impl/persistence/mvstore/src/test/resources/listen-to-any.yaml b/impl/persistence/mvstore/src/test/resources/listen-to-any.yaml new file mode 100644 index 00000000..b4a9fcb9 --- /dev/null +++ b/impl/persistence/mvstore/src/test/resources/listen-to-any.yaml @@ -0,0 +1,10 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: listen-to-any + version: '0.1.0' +do: + - callDoctor: + listen: + to: + any: [] \ No newline at end of file diff --git a/impl/persistence/mvstore/test.db b/impl/persistence/mvstore/test.db new file mode 100644 index 0000000000000000000000000000000000000000..1ffa67df30be3ea4bb7ad718607265d5d25ca2cd GIT binary patch literal 16384 zcmeI(&x+GP90%}3@edV!08btiFG-lmKa$+s?V%_IvCFE*Y%;MiX{Kgow^i|NJo^GZ zjy{N!NxOxz>lUi8u-_bpHq10LpFrXJ%VdjWh_slA{rPtAOHafKmY;|fB*y_009U<00I!W<^m(+|7(7S zFg*xB00Izz00bZa0SG_<0uX?}{sP(bw5il{eZ2=j)dFhPsBg1$uG?j|Bfx6dQYM}? zPd_lBy0o0j#8*272=S>OS+bm|odZQ?ov~JwT#bLS-_dO;m>a)M(|GWh&{yHxN&G$> z9v{6p42K7k;rJ+cbKt#uWRCRmr@wxFe`tgycLZzI6lx~Kg11#s^MzB+=C#9?^H!Zy zQxnd|PtMCUs|0VxJZH7~gt;$%M4)Qb^X=lG|zX*(MHk zh+Q^9rcK7g-A-cFJ-NEONe8`}8~Bl)IvNN-00Izz00bZa0SG_<0uZ<{0^9lfJVS4G z+DCp)@}LO!3)L5o9`x5=y#t^^{wDu>stT}7+;#rY{PJ|2_v`I@ z)|=Jk85=9Hh*;gn*ScD=cPT|LV29#@-!0(se*FSguXY@->okELbd}-uDAO~k>7tf# qWB#MCPzXQ(0uX=z1Rwwb2teT92yEx?b^doZ`9BU+{*V4s{{I8H&u|z3 literal 0 HcmV?d00001 diff --git a/impl/persistence/pom.xml b/impl/persistence/pom.xml index 71814078..8e26ebb3 100644 --- a/impl/persistence/pom.xml +++ b/impl/persistence/pom.xml @@ -9,6 +9,7 @@ Serverless Workflow :: Implementation:: Persistence pom + jackson-marshaller mvstore bigmap api diff --git a/impl/pom.xml b/impl/pom.xml index 352f8553..9309f6d7 100644 --- a/impl/pom.xml +++ b/impl/pom.xml @@ -36,6 +36,11 @@ serverlessworkflow-persistence-big-map ${project.version} + + io.serverlessworkflow + serverlessworkflow-persistence-jackson-marshaller + ${project.version} + io.serverlessworkflow serverlessworkflow-impl-jackson-jwt From aac9cda0b75b61a5c53b0739c7cc6ca6f3f477f5 Mon Sep 17 00:00:00 2001 From: fjtirado Date: Mon, 6 Oct 2025 16:25:30 +0200 Subject: [PATCH 4/7] [Fix #782] Moving tests around Signed-off-by: fjtirado --- impl/persistence/mvstore/pom.xml | 31 +----------------- .../src/test/resources/listen-to-any.yaml | 10 ------ impl/pom.xml | 11 +++++++ impl/test/pom.xml | 9 +++++ .../impl/test}/DBGenerator.java | 5 +-- .../impl/test}/MvStorePersistenceTest.java | 5 +-- impl/{persistence/mvstore => test}/test.db | Bin 16384 -> 20480 bytes 7 files changed, 27 insertions(+), 44 deletions(-) delete mode 100644 impl/persistence/mvstore/src/test/resources/listen-to-any.yaml rename impl/{persistence/mvstore/src/test/java/io/serverlessworkflow/tests/persistence => test/src/test/java/io/serverlessworkflow/impl/test}/DBGenerator.java (89%) rename impl/{persistence/mvstore/src/test/java/io/serverlessworkflow/tests/persistence => test/src/test/java/io/serverlessworkflow/impl/test}/MvStorePersistenceTest.java (93%) rename impl/{persistence/mvstore => test}/test.db (73%) diff --git a/impl/persistence/mvstore/pom.xml b/impl/persistence/mvstore/pom.xml index 469be107..28963d62 100644 --- a/impl/persistence/mvstore/pom.xml +++ b/impl/persistence/mvstore/pom.xml @@ -14,39 +14,10 @@ com.h2database h2-mvstore - ${version.com.h2database} - + io.serverlessworkflow serverlessworkflow-persistence-big-map - - io.serverlessworkflow - serverlessworkflow-api - test - - - io.serverlessworkflow - serverlessworkflow-impl-jackson - test - - - ch.qos.logback - logback-classic - test - - - io.serverlessworkflow - serverlessworkflow-persistence-jackson-marshaller - test - - - org.junit.jupiter - junit-jupiter-engine - - - org.assertj - assertj-core - \ No newline at end of file diff --git a/impl/persistence/mvstore/src/test/resources/listen-to-any.yaml b/impl/persistence/mvstore/src/test/resources/listen-to-any.yaml deleted file mode 100644 index b4a9fcb9..00000000 --- a/impl/persistence/mvstore/src/test/resources/listen-to-any.yaml +++ /dev/null @@ -1,10 +0,0 @@ -document: - dsl: '1.0.0-alpha5' - namespace: test - name: listen-to-any - version: '0.1.0' -do: - - callDoctor: - listen: - to: - any: [] \ No newline at end of file diff --git a/impl/pom.xml b/impl/pom.xml index 9309f6d7..f252b49b 100644 --- a/impl/pom.xml +++ b/impl/pom.xml @@ -9,6 +9,7 @@ Serverless Workflow :: Impl pom + 1.4.199 8.3.0 4.0.0 1.6.0 @@ -41,6 +42,11 @@ serverlessworkflow-persistence-jackson-marshaller ${project.version} + + io.serverlessworkflow + serverlessworkflow-persistence-mvstore + ${project.version} + io.serverlessworkflow serverlessworkflow-impl-jackson-jwt @@ -78,6 +84,11 @@ ${version.org.glassfish.jersey} test + + com.h2database + h2-mvstore + ${version.com.h2database} + diff --git a/impl/test/pom.xml b/impl/test/pom.xml index 1009da33..f861f9c5 100644 --- a/impl/test/pom.xml +++ b/impl/test/pom.xml @@ -16,6 +16,15 @@ io.serverlessworkflow serverlessworkflow-api + + io.serverlessworkflow + serverlessworkflow-persistence-mvstore + + + io.serverlessworkflow + serverlessworkflow-persistence-jackson-marshaller + test + io.serverlessworkflow serverlessworkflow-impl-http diff --git a/impl/persistence/mvstore/src/test/java/io/serverlessworkflow/tests/persistence/DBGenerator.java b/impl/test/src/test/java/io/serverlessworkflow/impl/test/DBGenerator.java similarity index 89% rename from impl/persistence/mvstore/src/test/java/io/serverlessworkflow/tests/persistence/DBGenerator.java rename to impl/test/src/test/java/io/serverlessworkflow/impl/test/DBGenerator.java index 56df139c..84a7ae25 100644 --- a/impl/persistence/mvstore/src/test/java/io/serverlessworkflow/tests/persistence/DBGenerator.java +++ b/impl/test/src/test/java/io/serverlessworkflow/impl/test/DBGenerator.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.serverlessworkflow.tests.persistence; +package io.serverlessworkflow.impl.test; import static io.serverlessworkflow.api.WorkflowReader.readWorkflowFromClasspath; @@ -31,7 +31,8 @@ public static void main(String[] args) throws IOException { WorkflowApplication application = BytesBigMapApplicationBuilder.builder(WorkflowApplication.builder(), store).build()) { WorkflowDefinition definition = - application.workflowDefinition(readWorkflowFromClasspath("listen-to-any.yaml")); + application.workflowDefinition( + readWorkflowFromClasspath("workflows-samples/listen-to-any.yaml")); definition.instance(Map.of()).start(); } } diff --git a/impl/persistence/mvstore/src/test/java/io/serverlessworkflow/tests/persistence/MvStorePersistenceTest.java b/impl/test/src/test/java/io/serverlessworkflow/impl/test/MvStorePersistenceTest.java similarity index 93% rename from impl/persistence/mvstore/src/test/java/io/serverlessworkflow/tests/persistence/MvStorePersistenceTest.java rename to impl/test/src/test/java/io/serverlessworkflow/impl/test/MvStorePersistenceTest.java index 7b4fa60d..f5564156 100644 --- a/impl/persistence/mvstore/src/test/java/io/serverlessworkflow/tests/persistence/MvStorePersistenceTest.java +++ b/impl/test/src/test/java/io/serverlessworkflow/impl/test/MvStorePersistenceTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.serverlessworkflow.tests.persistence; +package io.serverlessworkflow.impl.test; import static io.serverlessworkflow.api.WorkflowReader.readWorkflowFromClasspath; import static org.assertj.core.api.Assertions.assertThat; @@ -45,7 +45,8 @@ public void testRestoreWaitingInstance() throws IOException { WorkflowPersistenceRestorer restorer = new BytesBigMapPersistenceRestorer(store, bufferFactory); ) { WorkflowDefinition definition = - application.workflowDefinition(readWorkflowFromClasspath("listen-to-any.yaml")); + application.workflowDefinition( + readWorkflowFromClasspath("workflows-samples/listen-to-any.yaml")); Collection instances = restorer.restoreAll(definition).values(); assertThat(instances).hasSize(1); instances.forEach(WorkflowInstance::start); diff --git a/impl/persistence/mvstore/test.db b/impl/test/test.db similarity index 73% rename from impl/persistence/mvstore/test.db rename to impl/test/test.db index 1ffa67df30be3ea4bb7ad718607265d5d25ca2cd..4be6ebed83b5679c40e53ff49ab2cac608d410fc 100644 GIT binary patch literal 20480 zcmeI)OK#IZ90qW^;vs_I0IXPm*f`eA_)*6THmD*B3W^koitZd|LQNbycqVB8aT89! z5jX{RfD5o-&-js=M4^ourRw6#!cEdTc79V;`ab{Dw$F?~oQC6m^ojeF5MqQG4gNBA11Rwwb2tWV=5P$##AOHafKmY>ssg~S3IJ9X0A5i5*whQl0wAPbT>-F-$J+kMyaAYMzjR%@|A^36?(y5B*S(j$ z!%pY8*YCWrPx{@H?$JZNU995i&mZ3(Xl|66f@jJv=0cpM*?1JEXJ#~+#wK4(Gxelu zCxrR_gV_nfu}G6{I^?l>g@eyu)b@}5Tf3#*Ej&zmTQwjieU=0hZb~j>GmeDhNmHiH zAo@9j1JTh;Y{ z`=9)875U#%SNMwo0B3aoAgl%ehOEs0)Nxz%d-O{Pf&}>}KUo0F20uX=z1Rwwb2tWV= a5V)#9ndR#Iz0Chso&N(@<^RE+^8a6_c?mQC delta 514 zcmZozz}V2hI6*fCLcF&;w@$@XJBSv z%}p%OGgO}}=qNYY!HJWD4P-h)Wl+AAP3*%2K`w^#$d-8!5n7- zb(|r@aTZ|58JSudKpbb3Vq!em$H0f5aUG)-0|x_paz<%hww~eS4Gyw0SY4}Il31Kw z40qOK0Y@FioJk5|lTSG+nZj%(93(nrsYS(^`FU0d4^57AGVw4p%}WFZnt`FWSvVg1 db&`ux6H8LVV1_UnGa51)p@fGG@8kyi1^|oSmx2HQ From ca799b9317a6aad8c7c6bb619d0b1b42a1616d5b Mon Sep 17 00:00:00 2001 From: fjtirado Date: Tue, 7 Oct 2025 12:12:04 +0200 Subject: [PATCH 5/7] [Fix #782] More changes after testing Signed-off-by: fjtirado --- impl/README.md | 4 + .../impl/WorkflowMutableInstance.java | 2 +- .../impl/WorkflowUtils.java | 10 ++- .../impl/executors/AbstractTaskExecutor.java | 7 +- impl/persistence/README.md | 13 ++++ .../impl/marshaller/DefaultBufferFactory.java | 15 ++-- .../PersistenceApplicationBuilder.java | 40 ++++++++++ .../PersistenceInstanceHandlers.java | 44 +++++++++++ ...er.java => PersistenceInstanceReader.java} | 9 +-- ...er.java => PersistenceInstanceWriter.java} | 2 +- .../WorkflowPersistenceListener.java | 4 +- ...riter.java => BigMapIdInstanceWriter.java} | 6 +- ...estorer.java => BigMapInstanceReader.java} | 16 ++-- ...nceStore.java => BigMapInstanceStore.java} | 2 +- ...eWriter.java => BigMapInstanceWriter.java} | 8 +- .../bigmap/BytesBigMapApplicationBuilder.java | 55 ------------- ...torer.java => BytesMapInstanceReader.java} | 7 +- ...riter.java => BytesMapInstanceWriter.java} | 12 +-- .../BytesMapPersistenceInstanceHandlers.java | 72 ++++++++++++++++++ .../persistence/bigmap/MarshallingUtils.java | 5 -- impl/persistence/mvstore/README.md | 41 ++++++++++ .../mvstore/MVStorePersistenceStore.java | 4 +- .../impl/test/DBGenerator.java | 18 ++++- .../impl/test/MvStorePersistenceTest.java | 54 +++++++++---- .../test/TaskCounterPerInstanceListener.java | 67 ++++++++++++++++ .../workflows-samples/set-listen-to-any.yaml | 13 ++++ impl/test/test.db | Bin 20480 -> 16384 bytes 27 files changed, 398 insertions(+), 132 deletions(-) create mode 100644 impl/persistence/README.md create mode 100644 impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceApplicationBuilder.java create mode 100644 impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceInstanceHandlers.java rename impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/{WorkflowPersistenceRestorer.java => PersistenceInstanceReader.java} (72%) rename impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/{WorkflowPersistenceWriter.java => PersistenceInstanceWriter.java} (95%) rename impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/{BigMapIdPersistenceWriter.java => BigMapIdInstanceWriter.java} (81%) rename impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/{BigMapPersistenceRestorer.java => BigMapInstanceReader.java} (85%) rename impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/{BigMapPersistenceStore.java => BigMapInstanceStore.java} (92%) rename impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/{BigMapPersistenceWriter.java => BigMapInstanceWriter.java} (91%) delete mode 100644 impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapApplicationBuilder.java rename impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/{BytesBigMapPersistenceRestorer.java => BytesMapInstanceReader.java} (91%) rename impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/{BytesBigMapPersistenceWriter.java => BytesMapInstanceWriter.java} (86%) create mode 100644 impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesMapPersistenceInstanceHandlers.java create mode 100644 impl/persistence/mvstore/README.md create mode 100644 impl/test/src/test/java/io/serverlessworkflow/impl/test/TaskCounterPerInstanceListener.java create mode 100644 impl/test/src/test/resources/workflows-samples/set-listen-to-any.yaml diff --git a/impl/README.md b/impl/README.md index d7649733..e041285d 100644 --- a/impl/README.md +++ b/impl/README.md @@ -213,6 +213,10 @@ As shown in previous examples, to start a new workflow instance, first a [Workfl Once started, and before it completes, a workflow instance execution can be suspended or cancelled. Once cancelled, a workflow instance is done, while a suspended one might be resumed. +## Persistence + +Workflow progress might be recorded into DB. See [details](persistence/README.md) + ## Fluent Java DSL Prefer building workflows programmatically with type-safe builders and recipes? diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowMutableInstance.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowMutableInstance.java index 51536aa8..6696b100 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowMutableInstance.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowMutableInstance.java @@ -62,7 +62,6 @@ public CompletableFuture start() { return startExecution( () -> { startedAt = Instant.now(); - status.set(WorkflowStatus.RUNNING); publishEvent( workflowContext, l -> l.onWorkflowStarted(new WorkflowStartedEvent(workflowContext))); }); @@ -73,6 +72,7 @@ protected final CompletableFuture startExecution(Runnable runnabl if (future != null) { return future; } + status.set(WorkflowStatus.RUNNING); runnable.run(); future = TaskExecutorHelper.processTaskList( diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java index b48945af..491dc2aa 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowUtils.java @@ -144,10 +144,12 @@ public static String toString(UriTemplate template) { } public static void safeClose(AutoCloseable closeable) { - try { - closeable.close(); - } catch (Exception ex) { - logger.warn("Error closing resource {}", closeable.getClass().getName(), ex); + if (closeable != null) { + try { + closeable.close(); + } catch (Exception ex) { + logger.warn("Error closing resource {}", closeable.getClass().getName(), ex); + } } } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java index 4193981d..5c80adc8 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/AbstractTaskExecutor.java @@ -194,10 +194,11 @@ public CompletableFuture apply( TaskContext taskContext = new TaskContext(input, position, parentContext, taskName, task); workflowContext.instance().restoreContext(workflowContext, taskContext); CompletableFuture completable = CompletableFuture.completedFuture(taskContext); - if (taskContext.isCompleted() && !TaskExecutorHelper.isActive(workflowContext)) { + if (!TaskExecutorHelper.isActive(workflowContext)) { return completable; - } - if (ifFilter.map(f -> f.test(workflowContext, taskContext, input)).orElse(true)) { + } else if (taskContext.isCompleted()) { + return executeNext(completable, workflowContext); + } else if (ifFilter.map(f -> f.test(workflowContext, taskContext, input)).orElse(true)) { return executeNext( completable .thenCompose(workflowContext.instance()::suspendedCheck) diff --git a/impl/persistence/README.md b/impl/persistence/README.md new file mode 100644 index 00000000..43b4ca84 --- /dev/null +++ b/impl/persistence/README.md @@ -0,0 +1,13 @@ +[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/serverlessworkflow/sdk-java) + +# Serverless Workflow Specification — Java SDK (Reference Implementation)- Persistence + +Workflow persistence aim is to be able to restore workflow instances execution in the event of a JVM stop. To do that, progress of every running instance is persisted into the underlying DB by using life cycle events. Later on, when a new JVM is instantiated, the application is expected to manually start those instances that are not longer being executed by any other JVM, using the information previously stored. + +Currently, persistence structure has been layout for key-value store dbs, plus one concrete implementation using [H2 MVStore](mvstore/README.md). Next step will do the same for relational dbs (table layout plus concrete implementation using Postgresql). + +Map of key values has been given precedence because, when persisting the status of a running workflow instance, the number of writes are usually large, while read only operations are only performed when the JVM starts up. This give a performance edge for this kind of db over relational ones. + +--- + +*Questions or ideas? PRs and issues welcome!* diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/DefaultBufferFactory.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/DefaultBufferFactory.java index 5d8a4b4d..ba3d2cc8 100644 --- a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/DefaultBufferFactory.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/marshaller/DefaultBufferFactory.java @@ -24,14 +24,19 @@ public class DefaultBufferFactory implements WorkflowBufferFactory { private final Collection marshallers; + private static class DefaultBufferFactoryHolder { + private static DefaultBufferFactory instance = + new DefaultBufferFactory( + ServiceLoader.load(CustomObjectMarshaller.class).stream() + .map(ServiceLoader.Provider::get) + .toList()); + } + public static DefaultBufferFactory factory() { - return new DefaultBufferFactory( - ServiceLoader.load(CustomObjectMarshaller.class).stream() - .map(ServiceLoader.Provider::get) - .toList()); + return DefaultBufferFactoryHolder.instance; } - public DefaultBufferFactory(Collection marshallers) { + protected DefaultBufferFactory(Collection marshallers) { this.marshallers = marshallers; } diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceApplicationBuilder.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceApplicationBuilder.java new file mode 100644 index 00000000..462cf6ca --- /dev/null +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceApplicationBuilder.java @@ -0,0 +1,40 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence; + +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowApplication.Builder; + +public class PersistenceApplicationBuilder { + + public static PersistenceApplicationBuilder builder( + WorkflowApplication.Builder builder, PersistenceInstanceWriter writer) { + return new PersistenceApplicationBuilder(builder, writer); + } + + private final PersistenceInstanceWriter writer; + private final WorkflowApplication.Builder appBuilder; + + protected PersistenceApplicationBuilder(Builder appBuilder, PersistenceInstanceWriter writer) { + this.appBuilder = appBuilder; + this.writer = writer; + } + + public WorkflowApplication build() { + appBuilder.withListener(new WorkflowPersistenceListener(writer)); + return appBuilder.build(); + } +} diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceInstanceHandlers.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceInstanceHandlers.java new file mode 100644 index 00000000..4d470af1 --- /dev/null +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceInstanceHandlers.java @@ -0,0 +1,44 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence; + +import static io.serverlessworkflow.impl.WorkflowUtils.safeClose; + +public abstract class PersistenceInstanceHandlers implements AutoCloseable { + + protected final PersistenceInstanceWriter writer; + protected final PersistenceInstanceReader reader; + + protected PersistenceInstanceHandlers( + PersistenceInstanceWriter writer, PersistenceInstanceReader reader) { + this.writer = writer; + this.reader = reader; + } + + public PersistenceInstanceWriter writer() { + return writer; + } + + public PersistenceInstanceReader reader() { + return reader; + } + + @Override + public void close() { + safeClose(writer); + safeClose(reader); + } +} diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceRestorer.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceInstanceReader.java similarity index 72% rename from impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceRestorer.java rename to impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceInstanceReader.java index b1aa0093..5678e894 100644 --- a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceRestorer.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceInstanceReader.java @@ -21,13 +21,12 @@ import java.util.Map; import java.util.Optional; -public interface WorkflowPersistenceRestorer extends AutoCloseable { - Map restoreAll(WorkflowDefinition definition); +public interface PersistenceInstanceReader extends AutoCloseable { + Map readAll(WorkflowDefinition definition); - Map restore( - WorkflowDefinition definition, Collection instanceIds); + Map read(WorkflowDefinition definition, Collection instanceIds); - Optional restore(WorkflowDefinition definition, String instanceId); + Optional read(WorkflowDefinition definition, String instanceId); @Override default void close() {} diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceWriter.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceInstanceWriter.java similarity index 95% rename from impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceWriter.java rename to impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceInstanceWriter.java index 57ea620c..ae75ef38 100644 --- a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceWriter.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/PersistenceInstanceWriter.java @@ -18,7 +18,7 @@ import io.serverlessworkflow.impl.TaskContextData; import io.serverlessworkflow.impl.WorkflowContextData; -public interface WorkflowPersistenceWriter extends AutoCloseable { +public interface PersistenceInstanceWriter extends AutoCloseable { void started(WorkflowContextData workflowContext); diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceListener.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceListener.java index 60d8b723..d03da00d 100644 --- a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceListener.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceListener.java @@ -29,9 +29,9 @@ public class WorkflowPersistenceListener implements WorkflowExecutionListener { - private final WorkflowPersistenceWriter persistenceWriter; + private final PersistenceInstanceWriter persistenceWriter; - public WorkflowPersistenceListener(WorkflowPersistenceWriter persistenceWriter) { + public WorkflowPersistenceListener(PersistenceInstanceWriter persistenceWriter) { this.persistenceWriter = persistenceWriter; } diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapIdPersistenceWriter.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapIdInstanceWriter.java similarity index 81% rename from impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapIdPersistenceWriter.java rename to impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapIdInstanceWriter.java index 55cd1fcd..25ca9e4f 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapIdPersistenceWriter.java +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapIdInstanceWriter.java @@ -17,10 +17,10 @@ import io.serverlessworkflow.impl.WorkflowContextData; -public abstract class BigMapIdPersistenceWriter - extends BigMapPersistenceWriter { +public abstract class BigMapIdInstanceWriter + extends BigMapInstanceWriter { - protected BigMapIdPersistenceWriter(BigMapPersistenceStore store) { + protected BigMapIdInstanceWriter(BigMapInstanceStore store) { super(store); } diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceRestorer.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceReader.java similarity index 85% rename from impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceRestorer.java rename to impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceReader.java index c1e0f731..cc8896cc 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceRestorer.java +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceReader.java @@ -18,26 +18,26 @@ import io.serverlessworkflow.impl.WorkflowDefinition; import io.serverlessworkflow.impl.WorkflowInstance; import io.serverlessworkflow.impl.WorkflowStatus; +import io.serverlessworkflow.impl.persistence.PersistenceInstanceReader; import io.serverlessworkflow.impl.persistence.PersistenceTaskInfo; import io.serverlessworkflow.impl.persistence.PersistenceWorkflowInfo; import io.serverlessworkflow.impl.persistence.WorkflowPersistenceInstance; -import io.serverlessworkflow.impl.persistence.WorkflowPersistenceRestorer; import java.util.Collection; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.stream.Collectors; -public abstract class BigMapPersistenceRestorer implements WorkflowPersistenceRestorer { +public abstract class BigMapInstanceReader implements PersistenceInstanceReader { - private final BigMapPersistenceStore store; + private final BigMapInstanceStore store; - protected BigMapPersistenceRestorer(BigMapPersistenceStore store) { + protected BigMapInstanceReader(BigMapInstanceStore store) { this.store = store; } @Override - public Map restoreAll(WorkflowDefinition definition) { + public Map readAll(WorkflowDefinition definition) { Map instances = store.instanceData(definition); Map status = store.status(definition); return instances.entrySet().stream() @@ -53,16 +53,16 @@ public Map restoreAll(WorkflowDefinition definition) { } @Override - public Map restore( + public Map read( WorkflowDefinition definition, Collection instanceIds) { return instanceIds.stream() - .map(id -> restore(definition, id)) + .map(id -> read(definition, id)) .flatMap(Optional::stream) .collect(Collectors.toMap(WorkflowInstance::id, id -> id)); } @Override - public Optional restore(WorkflowDefinition definition, String instanceId) { + public Optional read(WorkflowDefinition definition, String instanceId) { Map instances = store.instanceData(definition); return instances.containsKey(instanceId) ? Optional.empty() diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceStore.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceStore.java similarity index 92% rename from impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceStore.java rename to impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceStore.java index a24781f1..d9d4c1ad 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceStore.java +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceStore.java @@ -18,7 +18,7 @@ import io.serverlessworkflow.impl.WorkflowDefinitionData; import java.util.Map; -public interface BigMapPersistenceStore extends AutoCloseable { +public interface BigMapInstanceStore extends AutoCloseable { Map instanceData(WorkflowDefinitionData definition); diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceWriter.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceWriter.java similarity index 91% rename from impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceWriter.java rename to impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceWriter.java index ee986084..9532d005 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapPersistenceWriter.java +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceWriter.java @@ -20,13 +20,13 @@ import io.serverlessworkflow.impl.WorkflowContextData; import io.serverlessworkflow.impl.WorkflowInstanceData; import io.serverlessworkflow.impl.WorkflowStatus; -import io.serverlessworkflow.impl.persistence.WorkflowPersistenceWriter; +import io.serverlessworkflow.impl.persistence.PersistenceInstanceWriter; -public abstract class BigMapPersistenceWriter implements WorkflowPersistenceWriter { +public abstract class BigMapInstanceWriter implements PersistenceInstanceWriter { - private BigMapPersistenceStore store; + private BigMapInstanceStore store; - protected BigMapPersistenceWriter(BigMapPersistenceStore store) { + protected BigMapInstanceWriter(BigMapInstanceStore store) { this.store = store; } diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapApplicationBuilder.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapApplicationBuilder.java deleted file mode 100644 index e8281deb..00000000 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapApplicationBuilder.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2020-Present The Serverless Workflow Specification Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.serverlessworkflow.impl.persistence.bigmap; - -import io.serverlessworkflow.impl.WorkflowApplication; -import io.serverlessworkflow.impl.WorkflowApplication.Builder; -import io.serverlessworkflow.impl.marshaller.DefaultBufferFactory; -import io.serverlessworkflow.impl.marshaller.WorkflowBufferFactory; -import io.serverlessworkflow.impl.persistence.WorkflowPersistenceListener; - -public class BytesBigMapApplicationBuilder { - - public static BytesBigMapApplicationBuilder builder( - WorkflowApplication.Builder builder, - BigMapPersistenceStore store) { - return new BytesBigMapApplicationBuilder(builder, store); - } - - private final BigMapPersistenceStore store; - private final WorkflowApplication.Builder appBuilder; - private WorkflowBufferFactory factory; - - protected BytesBigMapApplicationBuilder( - Builder appBuilder, BigMapPersistenceStore store) { - this.appBuilder = appBuilder; - this.store = store; - } - - public BytesBigMapApplicationBuilder withFactory(WorkflowBufferFactory factory) { - this.factory = factory; - return this; - } - - public WorkflowApplication build() { - if (factory == null) { - factory = DefaultBufferFactory.factory(); - } - appBuilder.withListener( - new WorkflowPersistenceListener(new BytesBigMapPersistenceWriter(store, factory))); - return appBuilder.build(); - } -} diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceRestorer.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesMapInstanceReader.java similarity index 91% rename from impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceRestorer.java rename to impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesMapInstanceReader.java index 5a552528..8b90ae18 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceRestorer.java +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesMapInstanceReader.java @@ -23,13 +23,12 @@ import java.io.ByteArrayInputStream; import java.time.Instant; -public class BytesBigMapPersistenceRestorer - extends BigMapPersistenceRestorer { +public class BytesMapInstanceReader extends BigMapInstanceReader { private final WorkflowBufferFactory factory; - public BytesBigMapPersistenceRestorer( - BigMapPersistenceStore store, WorkflowBufferFactory factory) { + public BytesMapInstanceReader( + BigMapInstanceStore store, WorkflowBufferFactory factory) { super(store); this.factory = factory; } diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceWriter.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesMapInstanceWriter.java similarity index 86% rename from impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceWriter.java rename to impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesMapInstanceWriter.java index 419b7800..320d7eff 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesBigMapPersistenceWriter.java +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesMapInstanceWriter.java @@ -24,16 +24,14 @@ import io.serverlessworkflow.impl.executors.TaskExecutor; import io.serverlessworkflow.impl.marshaller.WorkflowBufferFactory; import io.serverlessworkflow.impl.marshaller.WorkflowOutputBuffer; -import io.serverlessworkflow.impl.persistence.bigmap.MarshallingUtils.TaskStatus; import java.io.ByteArrayOutputStream; -public class BytesBigMapPersistenceWriter - extends BigMapIdPersistenceWriter { +public class BytesMapInstanceWriter extends BigMapIdInstanceWriter { private final WorkflowBufferFactory factory; - public BytesBigMapPersistenceWriter( - BigMapPersistenceStore store, WorkflowBufferFactory factory) { + public BytesMapInstanceWriter( + BigMapInstanceStore store, WorkflowBufferFactory factory) { super(store); this.factory = factory; } @@ -60,10 +58,6 @@ protected byte[] marshallTaskCompleted(WorkflowContextData contextData, TaskCont return bytes.toByteArray(); } - protected void writeTaskStatus(WorkflowOutputBuffer buffer, TaskStatus taskStatus) { - buffer.writeByte((byte) taskStatus.ordinal()); - } - @Override protected byte[] marshallStatus(WorkflowStatus status) { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesMapPersistenceInstanceHandlers.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesMapPersistenceInstanceHandlers.java new file mode 100644 index 00000000..e7767888 --- /dev/null +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BytesMapPersistenceInstanceHandlers.java @@ -0,0 +1,72 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence.bigmap; + +import static io.serverlessworkflow.impl.WorkflowUtils.safeClose; + +import io.serverlessworkflow.impl.marshaller.DefaultBufferFactory; +import io.serverlessworkflow.impl.marshaller.WorkflowBufferFactory; +import io.serverlessworkflow.impl.persistence.PersistenceInstanceHandlers; +import io.serverlessworkflow.impl.persistence.PersistenceInstanceReader; +import io.serverlessworkflow.impl.persistence.PersistenceInstanceWriter; + +public class BytesMapPersistenceInstanceHandlers extends PersistenceInstanceHandlers + implements AutoCloseable { + + private final BigMapInstanceStore store; + + protected BytesMapPersistenceInstanceHandlers( + PersistenceInstanceWriter writer, + PersistenceInstanceReader reader, + BigMapInstanceStore store) { + super(writer, reader); + this.store = store; + } + + public static class Builder { + private final BigMapInstanceStore store; + private WorkflowBufferFactory factory; + + private Builder(BigMapInstanceStore store) { + this.store = store; + } + + public Builder withFactory(WorkflowBufferFactory factory) { + this.factory = factory; + return this; + } + + public PersistenceInstanceHandlers build() { + if (factory == null) { + factory = DefaultBufferFactory.factory(); + } + return new BytesMapPersistenceInstanceHandlers( + new BytesMapInstanceWriter(store, factory), + new BytesMapInstanceReader(store, factory), + store); + } + } + + public static Builder builder(BigMapInstanceStore store) { + return new Builder(store); + } + + @Override + public void close() { + super.close(); + safeClose(store); + } +} diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/MarshallingUtils.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/MarshallingUtils.java index 87006938..2fea0224 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/MarshallingUtils.java +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/MarshallingUtils.java @@ -20,9 +20,4 @@ class MarshallingUtils { private MarshallingUtils() {} public static final byte VERSION_0 = 0; - - public enum TaskStatus { - STARTED, - COMPLETED - } } diff --git a/impl/persistence/mvstore/README.md b/impl/persistence/mvstore/README.md new file mode 100644 index 00000000..ff67da68 --- /dev/null +++ b/impl/persistence/mvstore/README.md @@ -0,0 +1,41 @@ +[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/serverlessworkflow/sdk-java) + +# Serverless Workflow Specification — Java SDK (Reference Implementation)- Persistence - MVStore + + +This document explains how to enable persistence using MVStore as underlying persistent mechanism. It is assumed that the reader is familiar with [standard workflow execution mechanism](../../README.md). + +To enable MVStore persistence, users should at least do the following things: + +- Initialize a MVStorePersistenceStore instance, passing the path of the file containing the persisted information +- Pass this MVStorePersitenceStore as argument of BytesMapPersistenceInstanceHandlers.builder. This will create PersistenceInstanceWriter and PersistenceInstanceReader. +- Use the PersistenceInstanceWriter created in the previous step to decorate the existing WorkflowApplication builder. + +The code will look like this + +---- + try (PersistenceInstanceHandlers handlers = + BytesMapPersistenceInstanceHandlers.builder(new MVStorePersistenceStore("test.db")) + .build(); + WorkflowApplication application = + PersistenceApplicationBuilder.builder( + WorkflowApplication.builder(), + handlers.writer()) + .build(); ) + { + // run workflow normally, the progress will be persisted + } +---- + + +If user wants to resume execution of all previously existing instances (typically after a server crash), he can use the reader created in the previous block to retrieve all stored instances. + +Once retrieved, calling `start` method will resume the execution after the latest completed task before the running JVM was stopped. + +---- + handlers.reader().readAll(definition).values().forEach(WorkflowInstance::start); +---- + +--- + +*Questions or ideas? PRs and issues welcome!* diff --git a/impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStorePersistenceStore.java b/impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStorePersistenceStore.java index 5baa4341..18e863df 100644 --- a/impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStorePersistenceStore.java +++ b/impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStorePersistenceStore.java @@ -18,12 +18,12 @@ import io.serverlessworkflow.api.types.Document; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.WorkflowDefinitionData; -import io.serverlessworkflow.impl.persistence.bigmap.BigMapPersistenceStore; +import io.serverlessworkflow.impl.persistence.bigmap.BigMapInstanceStore; import java.util.Map; import org.h2.mvstore.MVStore; public class MVStorePersistenceStore - implements BigMapPersistenceStore { + implements BigMapInstanceStore { private final MVStore mvStore; protected static final String ID_SEPARATOR = "-"; diff --git a/impl/test/src/test/java/io/serverlessworkflow/impl/test/DBGenerator.java b/impl/test/src/test/java/io/serverlessworkflow/impl/test/DBGenerator.java index 84a7ae25..29304c4a 100644 --- a/impl/test/src/test/java/io/serverlessworkflow/impl/test/DBGenerator.java +++ b/impl/test/src/test/java/io/serverlessworkflow/impl/test/DBGenerator.java @@ -19,20 +19,30 @@ import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowDefinition; -import io.serverlessworkflow.impl.persistence.bigmap.BytesBigMapApplicationBuilder; +import io.serverlessworkflow.impl.persistence.PersistenceApplicationBuilder; +import io.serverlessworkflow.impl.persistence.PersistenceInstanceHandlers; +import io.serverlessworkflow.impl.persistence.bigmap.BytesMapPersistenceInstanceHandlers; import io.serverlessworkflow.impl.persistence.mvstore.MVStorePersistenceStore; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Map; public class DBGenerator { public static void main(String[] args) throws IOException { - try (MVStorePersistenceStore store = new MVStorePersistenceStore("test.db"); + Files.deleteIfExists(Path.of("test.db")); + try (PersistenceInstanceHandlers factories = + BytesMapPersistenceInstanceHandlers.builder(new MVStorePersistenceStore("test.db")) + .build(); WorkflowApplication application = - BytesBigMapApplicationBuilder.builder(WorkflowApplication.builder(), store).build()) { + PersistenceApplicationBuilder.builder( + WorkflowApplication.builder().withListener(new TraceExecutionListener()), + factories.writer()) + .build()) { WorkflowDefinition definition = application.workflowDefinition( - readWorkflowFromClasspath("workflows-samples/listen-to-any.yaml")); + readWorkflowFromClasspath("workflows-samples/set-listen-to-any.yaml")); definition.instance(Map.of()).start(); } } diff --git a/impl/test/src/test/java/io/serverlessworkflow/impl/test/MvStorePersistenceTest.java b/impl/test/src/test/java/io/serverlessworkflow/impl/test/MvStorePersistenceTest.java index f5564156..3e74c5b3 100644 --- a/impl/test/src/test/java/io/serverlessworkflow/impl/test/MvStorePersistenceTest.java +++ b/impl/test/src/test/java/io/serverlessworkflow/impl/test/MvStorePersistenceTest.java @@ -22,37 +22,59 @@ import io.serverlessworkflow.impl.WorkflowDefinition; import io.serverlessworkflow.impl.WorkflowInstance; import io.serverlessworkflow.impl.WorkflowStatus; -import io.serverlessworkflow.impl.marshaller.DefaultBufferFactory; -import io.serverlessworkflow.impl.marshaller.WorkflowBufferFactory; -import io.serverlessworkflow.impl.persistence.WorkflowPersistenceRestorer; -import io.serverlessworkflow.impl.persistence.bigmap.BytesBigMapApplicationBuilder; -import io.serverlessworkflow.impl.persistence.bigmap.BytesBigMapPersistenceRestorer; +import io.serverlessworkflow.impl.persistence.PersistenceApplicationBuilder; +import io.serverlessworkflow.impl.persistence.PersistenceInstanceHandlers; +import io.serverlessworkflow.impl.persistence.bigmap.BytesMapPersistenceInstanceHandlers; import io.serverlessworkflow.impl.persistence.mvstore.MVStorePersistenceStore; import java.io.IOException; import java.util.Collection; +import java.util.Map; import org.junit.jupiter.api.Test; public class MvStorePersistenceTest { @Test - public void testRestoreWaitingInstance() throws IOException { - WorkflowBufferFactory bufferFactory = DefaultBufferFactory.factory(); - try (MVStorePersistenceStore store = new MVStorePersistenceStore("test.db"); - WorkflowApplication application = - BytesBigMapApplicationBuilder.builder(WorkflowApplication.builder(), store) - .withFactory(bufferFactory) + void testWaitingInstance() throws IOException { + TaskCounterPerInstanceListener taskCounter = new TaskCounterPerInstanceListener(); + try (WorkflowApplication application = + WorkflowApplication.builder().withListener(taskCounter).build()) { + WorkflowDefinition definition = + application.workflowDefinition( + readWorkflowFromClasspath("workflows-samples/set-listen-to-any.yaml")); + + WorkflowInstance instance = definition.instance(Map.of()); + instance.start(); + assertThat(taskCounter.taskCounter(instance.id()).orElseThrow().completed()).isEqualTo(1); + } + } + + @Test + void testRestoreWaitingInstance() throws IOException { + TaskCounterPerInstanceListener taskCounter = new TaskCounterPerInstanceListener(); + try (PersistenceInstanceHandlers handlers = + BytesMapPersistenceInstanceHandlers.builder(new MVStorePersistenceStore("test.db")) .build(); - WorkflowPersistenceRestorer restorer = - new BytesBigMapPersistenceRestorer(store, bufferFactory); ) { + WorkflowApplication application = + PersistenceApplicationBuilder.builder( + WorkflowApplication.builder() + .withListener(taskCounter) + .withListener(new TraceExecutionListener()), + handlers.writer()) + .build(); ) { WorkflowDefinition definition = application.workflowDefinition( - readWorkflowFromClasspath("workflows-samples/listen-to-any.yaml")); - Collection instances = restorer.restoreAll(definition).values(); + readWorkflowFromClasspath("workflows-samples/set-listen-to-any.yaml")); + Collection instances = handlers.reader().readAll(definition).values(); assertThat(instances).hasSize(1); instances.forEach(WorkflowInstance::start); assertThat(instances) .singleElement() - .satisfies((instance -> assertThat(instance.status()).isEqualTo(WorkflowStatus.WAITING))); + .satisfies( + instance -> { + assertThat(instance.status()).isEqualTo(WorkflowStatus.WAITING); + assertThat(taskCounter.taskCounter(instance.id()).orElseThrow().completed()) + .isEqualTo(0); + }); } } } diff --git a/impl/test/src/test/java/io/serverlessworkflow/impl/test/TaskCounterPerInstanceListener.java b/impl/test/src/test/java/io/serverlessworkflow/impl/test/TaskCounterPerInstanceListener.java new file mode 100644 index 00000000..dd3c7d06 --- /dev/null +++ b/impl/test/src/test/java/io/serverlessworkflow/impl/test/TaskCounterPerInstanceListener.java @@ -0,0 +1,67 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.test; + +import io.serverlessworkflow.impl.lifecycle.TaskCompletedEvent; +import io.serverlessworkflow.impl.lifecycle.TaskStartedEvent; +import io.serverlessworkflow.impl.lifecycle.WorkflowEvent; +import io.serverlessworkflow.impl.lifecycle.WorkflowExecutionListener; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +public class TaskCounterPerInstanceListener implements WorkflowExecutionListener { + + public static class TaskCounter { + private int started; + private int completed; + + public void incStarted() { + started++; + } + + public void incCompleted() { + completed++; + } + + public int started() { + return started; + } + + public int completed() { + return completed; + } + } + + private Map taskCounter = new ConcurrentHashMap<>(); + + public void onTaskStarted(TaskStartedEvent ev) { + taskCounter(ev).incStarted(); + } + + private TaskCounter taskCounter(WorkflowEvent ev) { + return taskCounter.computeIfAbsent( + ev.workflowContext().instanceData().id(), k -> new TaskCounter()); + } + + public Optional taskCounter(String instanceId) { + return Optional.ofNullable(taskCounter.get(instanceId)); + } + + public void onTaskCompleted(TaskCompletedEvent ev) { + taskCounter(ev).incCompleted(); + } +} diff --git a/impl/test/src/test/resources/workflows-samples/set-listen-to-any.yaml b/impl/test/src/test/resources/workflows-samples/set-listen-to-any.yaml new file mode 100644 index 00000000..d82936e9 --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/set-listen-to-any.yaml @@ -0,0 +1,13 @@ +document: + dsl: '1.0.0-alpha5' + namespace: test + name: set-listen-to-any + version: '0.1.0' +do: + - doSomethingBeforeEvent: + set: + name: javierito + - callDoctor: + listen: + to: + any: [] \ No newline at end of file diff --git a/impl/test/test.db b/impl/test/test.db index 4be6ebed83b5679c40e53ff49ab2cac608d410fc..b9c4c2afbe4afddeec0b41c0763b80e9da4f5f6d 100644 GIT binary patch literal 16384 zcmeI3-)_?|6o(Teh8QqzxPlv02?>cLbN;kTb2%}nHVsW0nt(emJz29Raf!WdF!mH& z@H{*U(s%`~aguIB)nJo4q)Geb!f~Uz$NpSZ`c98~k!NjZA|7t0pDA(~V^&-eSJaQ( zZg-nJFW^gwm54IuDsrtPBNY#*jKa=#*h!XX-oOO{AOHd&00JNY0w4eaAOHd&00JOz z%?b42|JQtnFg*|e0T2KI5C8!X009sH0T2KI5SX1nJlHFS`hWep20-TmGAi_|oR4%r z=LeA&Fsq;g75UbP?~v5@QV10Vx>A6#&Jv3)mFBu~An~m+l`<82{L|U4s-$3K9Gj+b z?=f@NIv>1Xb;DoTS_`_b!uRib%WpSdvo{N7uU`J(_~)+&Mkf`Hq*6~IBPsVqIZQIK z@1*%CbLe1H>W6x2(%Jp!tnzp$MX@gWl<7~nb$F!PZ|2uqI^L7MSY(TRu_~Tiz(WCt-=1)Yw+`nm01h3+MDv_e0m2Pf>}DIb)7$P zb^fI4Z`8??DyK42X65SbJgk=eZ{Kg0JvkS$b&}+%wBGa~_BZ2q3abPG5C8!X009sH c0T2KI5SX37w11!Yzjx;U-M04s@IU$gZv>9UO#lD@ literal 20480 zcmeI)OK#IZ90qW^;vs_I0IXPm*f`eA_)*6THmD*B3W^koitZd|LQNbycqVB8aT89! z5jX{RfD5o-&-js=M4^ourRw6#!cEdTc79V;`ab{Dw$F?~oQC6m^ojeF5MqQG4gNBA11Rwwb2tWV=5P$##AOHafKmY>ssg~S3IJ9X0A5i5*whQl0wAPbT>-F-$J+kMyaAYMzjR%@|A^36?(y5B*S(j$ z!%pY8*YCWrPx{@H?$JZNU995i&mZ3(Xl|66f@jJv=0cpM*?1JEXJ#~+#wK4(Gxelu zCxrR_gV_nfu}G6{I^?l>g@eyu)b@}5Tf3#*Ej&zmTQwjieU=0hZb~j>GmeDhNmHiH zAo@9j1JTh;Y{ z`=9)875U#%SNMwo0B3aoAgl%ehOEs0)Nxz%d-O{Pf&}>}KUo0F20uX=z1Rwwb2tWV= a5V)#9ndR#Iz0Chso&N(@<^RE+^8a6_c?mQC From 3ab34c091f9f7f055c30c0626ba7cd9255865b8f Mon Sep 17 00:00:00 2001 From: fjtirado Date: Tue, 7 Oct 2025 19:31:29 +0200 Subject: [PATCH 6/7] [Fix #782] Added suspend support Signed-off-by: fjtirado --- .../impl/WorkflowMutableInstance.java | 26 ++++++++++++------ .../WorkflowPersistenceInstance.java | 4 +++ impl/test/{test.db => db-samples/running.db} | Bin 16384 -> 16384 bytes impl/test/db-samples/suspended.db | Bin 0 -> 12288 bytes .../impl/test/DBGenerator.java | 16 +++++++++-- .../impl/test/MvStorePersistenceTest.java | 18 ++++++++---- .../test/TaskCounterPerInstanceListener.java | 8 ++---- 7 files changed, 50 insertions(+), 22 deletions(-) rename impl/test/{test.db => db-samples/running.db} (93%) create mode 100644 impl/test/db-samples/suspended.db diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowMutableInstance.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowMutableInstance.java index 6696b100..7409a1db 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowMutableInstance.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowMutableInstance.java @@ -147,17 +147,21 @@ public WorkflowStatus status() { @Override public WorkflowModel output() { - return futureRef.get().join(); + CompletableFuture future = futureRef.get(); + return future != null ? future.join() : null; } @Override public T outputAs(Class clazz) { - return output() - .as(clazz) - .orElseThrow( - () -> - new IllegalArgumentException( - "Output " + output() + " cannot be converted to class " + clazz)); + WorkflowModel output = output(); + return output != null + ? output + .as(clazz) + .orElseThrow( + () -> + new IllegalArgumentException( + "Output " + output + " cannot be converted to class " + clazz)) + : null; } public void status(WorkflowStatus state) { @@ -182,8 +186,7 @@ public boolean suspend() { try { statusLock.lock(); if (TaskExecutorHelper.isActive(status.get()) && suspended == null) { - suspended = new ConcurrentHashMap<>(); - status.set(WorkflowStatus.SUSPENDED); + internalSuspend(); publishEvent( workflowContext, l -> l.onWorkflowSuspended(new WorkflowSuspendedEvent(workflowContext))); @@ -196,6 +199,11 @@ public boolean suspend() { } } + protected final void internalSuspend() { + suspended = new ConcurrentHashMap<>(); + status.set(WorkflowStatus.SUSPENDED); + } + @Override public boolean resume() { try { diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceInstance.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceInstance.java index d266bb32..36a10fd4 100644 --- a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceInstance.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceInstance.java @@ -20,6 +20,7 @@ import io.serverlessworkflow.impl.WorkflowDefinition; import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowMutableInstance; +import io.serverlessworkflow.impl.WorkflowStatus; import io.serverlessworkflow.impl.executors.TransitionInfo; import java.util.concurrent.CompletableFuture; @@ -37,6 +38,9 @@ public CompletableFuture start() { return startExecution( () -> { startedAt = info.startedAt(); + if (info.status() == WorkflowStatus.SUSPENDED) { + internalSuspend(); + } }); } diff --git a/impl/test/test.db b/impl/test/db-samples/running.db similarity index 93% rename from impl/test/test.db rename to impl/test/db-samples/running.db index b9c4c2afbe4afddeec0b41c0763b80e9da4f5f6d..c9786b47cbc35d85d03bb7265a1772d6817b798b 100644 GIT binary patch delta 299 zcmZo@U~Fh$oFK21mY9^9XqcjtmS2>cSYl{l4n6lCF9zPMv zHaiM%2r#7>O!iiYQVae hnLI#UWvNBQnfZBEhLZ)A%&{xq{M)dZcXES$0{{_3TQ&dy delta 297 zcmZo@U~Fh$oFK21YGh=RXr88%mS2>cSYlCkrZ>V^_ZUuVFLqzeE*<|`WaL0v8ro;R*a7HP0lL~}^l(SA} zoQW}v0o+Vgr7)biSpg=QK*i&66lbZ(8U!~KKmim$0Te(16hHwKKmim$0Te)i8!pht z_5Tf@AuJCCPyhu`00mG01yBG5Pyhu`00q_;NG8W}YX8@-djRZj0R>2VR*D%9ZCvos zHUvmm7<)PshoE`nR!SKj*-imUx5Efrlb5!0Aav(Y>0H^{pRBhnI|^px+;Pa=N0hyc z4@Uc=gBNeM!_H{`!*2Us^nS#8gDt0Dzy0KV@5g--=gQYm*%AuS`b1UJtWYO@Ud{?1 z7PHD;w54hP=%c?UlBrg5pi(I86E;rIZ2abY-LUx{r>aF;sT!&h%p{kGyI?yXpf?9; zwj6ma2_K#|r6{4vn@3`vLzNrVJiA96^1vo%twa<>S0zSOHe29)zjZl-A!Kv-cKEvY zs@v-l;ta?avPo{+3G`VVgF1#!mpT1^b9zSTseWFMGN6s8!FWZk4M@)@PsmRm^_fq- zTxug^0{Yq2W9>+DtnVy87@w_n!D<)QnfU)@s%z(0)n3 { - assertThat(instance.status()).isEqualTo(WorkflowStatus.WAITING); - assertThat(taskCounter.taskCounter(instance.id()).orElseThrow().completed()) - .isEqualTo(0); + assertThat(instance.status()).isEqualTo(expectedStatus); + assertThat(taskCounter.taskCounter(instance.id()).completed()).isEqualTo(0); }); } } diff --git a/impl/test/src/test/java/io/serverlessworkflow/impl/test/TaskCounterPerInstanceListener.java b/impl/test/src/test/java/io/serverlessworkflow/impl/test/TaskCounterPerInstanceListener.java index dd3c7d06..19fe6217 100644 --- a/impl/test/src/test/java/io/serverlessworkflow/impl/test/TaskCounterPerInstanceListener.java +++ b/impl/test/src/test/java/io/serverlessworkflow/impl/test/TaskCounterPerInstanceListener.java @@ -20,7 +20,6 @@ import io.serverlessworkflow.impl.lifecycle.WorkflowEvent; import io.serverlessworkflow.impl.lifecycle.WorkflowExecutionListener; import java.util.Map; -import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; public class TaskCounterPerInstanceListener implements WorkflowExecutionListener { @@ -53,12 +52,11 @@ public void onTaskStarted(TaskStartedEvent ev) { } private TaskCounter taskCounter(WorkflowEvent ev) { - return taskCounter.computeIfAbsent( - ev.workflowContext().instanceData().id(), k -> new TaskCounter()); + return taskCounter(ev.workflowContext().instanceData().id()); } - public Optional taskCounter(String instanceId) { - return Optional.ofNullable(taskCounter.get(instanceId)); + public TaskCounter taskCounter(String instanceId) { + return taskCounter.computeIfAbsent(instanceId, k -> new TaskCounter()); } public void onTaskCompleted(TaskCompletedEvent ev) { From d6cd06369bd853b88b0f527a14da1ad804763f96 Mon Sep 17 00:00:00 2001 From: fjtirado Date: Wed, 8 Oct 2025 09:42:19 +0200 Subject: [PATCH 7/7] [Fix #782] Adding transaction support Signed-off-by: fjtirado --- .../WorkflowPersistenceInstance.java | 2 +- .../bigmap/BigMapInstanceReader.java | 77 +++++++++++----- .../bigmap/BigMapInstanceStore.java | 12 +-- .../bigmap/BigMapInstanceTransaction.java | 34 +++++++ .../bigmap/BigMapInstanceWriter.java | 53 +++++++---- .../mvstore/MVStorePersistenceStore.java | 49 ++-------- .../mvstore/MVStoreTransaction.java | 84 ++++++++++++++++++ impl/test/db-samples/running.db | Bin 16384 -> 16384 bytes impl/test/db-samples/suspended.db | Bin 12288 -> 12288 bytes .../impl/test/MvStorePersistenceTest.java | 22 +++++ 10 files changed, 238 insertions(+), 95 deletions(-) create mode 100644 impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceTransaction.java create mode 100644 impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStoreTransaction.java diff --git a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceInstance.java b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceInstance.java index 36a10fd4..335b87d0 100644 --- a/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceInstance.java +++ b/impl/persistence/api/src/main/java/io/serverlessworkflow/impl/persistence/WorkflowPersistenceInstance.java @@ -46,7 +46,7 @@ public CompletableFuture start() { @Override public void restoreContext(WorkflowContext workflow, TaskContext context) { - PersistenceTaskInfo taskInfo = info.tasks().get(context.position().jsonPointer()); + PersistenceTaskInfo taskInfo = info.tasks().remove(context.position().jsonPointer()); if (taskInfo != null) { context.output(taskInfo.model()); context.completedAt(taskInfo.instant()); diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceReader.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceReader.java index cc8896cc..e12de9b8 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceReader.java +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceReader.java @@ -26,6 +26,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Optional; +import java.util.function.Function; import java.util.stream.Collectors; public abstract class BigMapInstanceReader implements PersistenceInstanceReader { @@ -36,43 +37,75 @@ protected BigMapInstanceReader(BigMapInstanceStore store) { this.store = store; } + private Result doTransaction( + Function, Result> operations) { + BigMapInstanceTransaction transaction = store.begin(); + try { + Result result = operations.apply(transaction); + transaction.commit(); + return result; + } catch (Exception ex) { + transaction.rollback(); + throw ex; + } + } + @Override public Map readAll(WorkflowDefinition definition) { - Map instances = store.instanceData(definition); - Map status = store.status(definition); - return instances.entrySet().stream() - .map( - e -> - restore( - definition, - e.getKey(), - e.getValue(), - store.tasks(e.getKey()), - status.get(e.getKey()))) - .collect(Collectors.toMap(WorkflowInstance::id, i -> i)); + return doTransaction( + t -> { + Map instances = t.instanceData(definition); + Map status = t.status(definition); + return instances.entrySet().stream() + .map( + e -> + restore( + definition, + e.getKey(), + e.getValue(), + t.tasks(e.getKey()), + status.get(e.getKey()))) + .collect(Collectors.toMap(WorkflowInstance::id, i -> i)); + }); } @Override public Map read( WorkflowDefinition definition, Collection instanceIds) { - return instanceIds.stream() - .map(id -> read(definition, id)) - .flatMap(Optional::stream) - .collect(Collectors.toMap(WorkflowInstance::id, id -> id)); + return doTransaction( + t -> { + Map instances = t.instanceData(definition); + Map status = t.status(definition); + return instanceIds.stream() + .map(id -> read(instances, status, t.tasks(id), definition, id)) + .flatMap(Optional::stream) + .collect(Collectors.toMap(WorkflowInstance::id, id -> id)); + }); } @Override public Optional read(WorkflowDefinition definition, String instanceId) { - Map instances = store.instanceData(definition); + return doTransaction( + t -> + read( + t.instanceData(definition), + t.status(definition), + t.tasks(instanceId), + definition, + instanceId)); + } + + private Optional read( + Map instances, + Map status, + Map tasks, + WorkflowDefinition definition, + String instanceId) { return instances.containsKey(instanceId) ? Optional.empty() : Optional.of( restore( - definition, - instanceId, - instances.get(instanceId), - store.tasks(instanceId), - store.status(definition).get(instanceId))); + definition, instanceId, instances.get(instanceId), tasks, status.get(instanceId))); } public void close() {} diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceStore.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceStore.java index d9d4c1ad..aa1d998e 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceStore.java +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceStore.java @@ -15,16 +15,6 @@ */ package io.serverlessworkflow.impl.persistence.bigmap; -import io.serverlessworkflow.impl.WorkflowDefinitionData; -import java.util.Map; - public interface BigMapInstanceStore extends AutoCloseable { - - Map instanceData(WorkflowDefinitionData definition); - - Map status(WorkflowDefinitionData workflowContext); - - Map tasks(K instanceId); - - void cleanupTasks(K instanceId); + BigMapInstanceTransaction begin(); } diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceTransaction.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceTransaction.java new file mode 100644 index 00000000..72b89ed1 --- /dev/null +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceTransaction.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence.bigmap; + +import io.serverlessworkflow.impl.WorkflowDefinitionData; +import java.util.Map; + +public interface BigMapInstanceTransaction { + + Map instanceData(WorkflowDefinitionData definition); + + Map status(WorkflowDefinitionData workflowContext); + + Map tasks(K instanceId); + + void cleanupTasks(K instanceId); + + void commit(); + + void rollback(); +} diff --git a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceWriter.java b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceWriter.java index 9532d005..77f96e24 100644 --- a/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceWriter.java +++ b/impl/persistence/bigmap/src/main/java/io/serverlessworkflow/impl/persistence/bigmap/BigMapInstanceWriter.java @@ -21,6 +21,7 @@ import io.serverlessworkflow.impl.WorkflowInstanceData; import io.serverlessworkflow.impl.WorkflowStatus; import io.serverlessworkflow.impl.persistence.PersistenceInstanceWriter; +import java.util.function.Consumer; public abstract class BigMapInstanceWriter implements PersistenceInstanceWriter { @@ -30,11 +31,23 @@ protected BigMapInstanceWriter(BigMapInstanceStore store) { this.store = store; } + private void doTransaction(Consumer> operations) { + BigMapInstanceTransaction transaction = store.begin(); + try { + operations.accept(transaction); + transaction.commit(); + } catch (Exception ex) { + transaction.rollback(); + throw ex; + } + } + @Override public void started(WorkflowContextData workflowContext) { - store - .instanceData(workflowContext.definition()) - .put(key(workflowContext), marshallInstance(workflowContext.instanceData())); + doTransaction( + t -> + t.instanceData(workflowContext.definition()) + .put(key(workflowContext), marshallInstance(workflowContext.instanceData()))); } @Override @@ -57,33 +70,35 @@ public void taskStarted(WorkflowContextData workflowContext, TaskContextData tas @Override public void taskCompleted(WorkflowContextData workflowContext, TaskContextData taskContext) { - K key = key(workflowContext); - store - .tasks(key) - .put( - taskContext.position().jsonPointer(), - marshallTaskCompleted(workflowContext, (TaskContext) taskContext)); + doTransaction( + t -> + t.tasks(key(workflowContext)) + .put( + taskContext.position().jsonPointer(), + marshallTaskCompleted(workflowContext, (TaskContext) taskContext))); } @Override public void suspended(WorkflowContextData workflowContext) { - store - .status(workflowContext.definition()) - .put(key(workflowContext), marshallStatus(WorkflowStatus.SUSPENDED)); + doTransaction( + t -> + t.status(workflowContext.definition()) + .put(key(workflowContext), marshallStatus(WorkflowStatus.SUSPENDED))); } @Override public void resumed(WorkflowContextData workflowContext) { - store - .status(workflowContext.definition()) - .put(key(workflowContext), marshallStatus(WorkflowStatus.RUNNING)); + doTransaction(t -> t.status(workflowContext.definition()).remove(key(workflowContext))); } protected void removeProcessInstance(WorkflowContextData workflowContext) { - K key = key(workflowContext); - store.instanceData(workflowContext.definition()).remove(key); - store.status(workflowContext.definition()).remove(key); - store.cleanupTasks(key); + doTransaction( + t -> { + K key = key(workflowContext); + t.instanceData(workflowContext.definition()).remove(key); + t.status(workflowContext.definition()).remove(key); + t.cleanupTasks(key); + }); } protected abstract K key(WorkflowContextData workflowContext); diff --git a/impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStorePersistenceStore.java b/impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStorePersistenceStore.java index 18e863df..0f206f9b 100644 --- a/impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStorePersistenceStore.java +++ b/impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStorePersistenceStore.java @@ -15,61 +15,26 @@ */ package io.serverlessworkflow.impl.persistence.mvstore; -import io.serverlessworkflow.api.types.Document; -import io.serverlessworkflow.api.types.Workflow; -import io.serverlessworkflow.impl.WorkflowDefinitionData; import io.serverlessworkflow.impl.persistence.bigmap.BigMapInstanceStore; -import java.util.Map; +import io.serverlessworkflow.impl.persistence.bigmap.BigMapInstanceTransaction; import org.h2.mvstore.MVStore; +import org.h2.mvstore.tx.TransactionStore; public class MVStorePersistenceStore implements BigMapInstanceStore { - private final MVStore mvStore; - - protected static final String ID_SEPARATOR = "-"; + private final TransactionStore mvStore; public MVStorePersistenceStore(String dbName) { - this.mvStore = MVStore.open(dbName); - } - - protected static String identifier(Workflow workflow, String sep) { - Document document = workflow.getDocument(); - return document.getNamespace() + sep + document.getName() + sep + document.getVersion(); + this.mvStore = new TransactionStore(MVStore.open(dbName)); } @Override public void close() { - if (!mvStore.isClosed()) { - mvStore.close(); - } - } - - @Override - public Map instanceData(WorkflowDefinitionData workflowContext) { - return openMap(workflowContext, "instances"); - } - - @Override - public Map tasks(String instanceId) { - return mvStore.openMap(mapTaskName(instanceId)); - } - - @Override - public Map status(WorkflowDefinitionData workflowContext) { - return openMap(workflowContext, "status"); - } - - private Map openMap(WorkflowDefinitionData workflowDefinition, String suffix) { - return mvStore.openMap( - identifier(workflowDefinition.workflow(), ID_SEPARATOR) + ID_SEPARATOR + suffix); - } - - private String mapTaskName(String instanceId) { - return instanceId + ID_SEPARATOR + "tasks"; + mvStore.close(); } @Override - public void cleanupTasks(String instanceId) { - mvStore.removeMap(mapTaskName(instanceId)); + public BigMapInstanceTransaction begin() { + return new MVStoreTransaction(mvStore.begin()); } } diff --git a/impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStoreTransaction.java b/impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStoreTransaction.java new file mode 100644 index 00000000..66b09499 --- /dev/null +++ b/impl/persistence/mvstore/src/main/java/io/serverlessworkflow/impl/persistence/mvstore/MVStoreTransaction.java @@ -0,0 +1,84 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.persistence.mvstore; + +import io.serverlessworkflow.api.types.Document; +import io.serverlessworkflow.api.types.Workflow; +import io.serverlessworkflow.impl.WorkflowDefinitionData; +import io.serverlessworkflow.impl.persistence.bigmap.BigMapInstanceTransaction; +import java.util.Map; +import org.h2.mvstore.tx.Transaction; +import org.h2.mvstore.tx.TransactionMap; + +public class MVStoreTransaction + implements BigMapInstanceTransaction { + + protected static final String ID_SEPARATOR = "-"; + + private final Transaction transaction; + + public MVStoreTransaction(Transaction transaction) { + this.transaction = transaction; + } + + protected static String identifier(Workflow workflow, String sep) { + Document document = workflow.getDocument(); + return document.getNamespace() + sep + document.getName() + sep + document.getVersion(); + } + + @Override + public Map instanceData(WorkflowDefinitionData workflowContext) { + return openMap(workflowContext, "instances"); + } + + @Override + public Map tasks(String instanceId) { + return taskMap(instanceId); + } + + @Override + public Map status(WorkflowDefinitionData workflowContext) { + return openMap(workflowContext, "status"); + } + + @Override + public void cleanupTasks(String instanceId) { + transaction.removeMap(taskMap(instanceId)); + } + + private TransactionMap taskMap(String instanceId) { + return transaction.openMap(mapTaskName(instanceId)); + } + + private Map openMap(WorkflowDefinitionData workflowDefinition, String suffix) { + return transaction.openMap( + identifier(workflowDefinition.workflow(), ID_SEPARATOR) + ID_SEPARATOR + suffix); + } + + private String mapTaskName(String instanceId) { + return instanceId + ID_SEPARATOR + "tasks"; + } + + @Override + public void commit() { + transaction.commit(); + } + + @Override + public void rollback() { + transaction.rollback(); + } +} diff --git a/impl/test/db-samples/running.db b/impl/test/db-samples/running.db index c9786b47cbc35d85d03bb7265a1772d6817b798b..29adc703bc8b36e30ab4cc089effa49387713558 100644 GIT binary patch delta 726 zcmZo@U~Fh$oFJ#1Y?NeblxSqAla^nUn^l+#5 zxVA4c2FEKY&54VlFC5gq^#iC$U`30$YAw`LK#fix!nfZB>?;Gek z@quMZ^HTDC^3(MU*^2V>OZ1E(lnGdy6`HnUuof%0mdS!jrq($83$dNigwc@En9+#K z!~g`0Oie7HOcO(k#EpT=c_ugL&tf+P2dVL7MFV*rOCz0<%-mEfV`FpU$-V|YY)sNj z{S3U5=Nm}7Lp%;9O)2w!m>gd3=T1@(n;c~*gA&NYzH+~nsl(#cJ%ure~4yv|UL$sl>MlR}tY0mBnUMh0#v14D1KsF1)AZ(~Rs>8a zW;9|nnI*1LJarG6r4- z*4)GbJwq^Q1SXBaqzRZbRn1GxP1VEgQr(io;_PD4JYkTs{DRcHkfOxA;>6^V%>2B` zyh>sId|(4g^HTDC^3(MU*+2oKXAGfCAe5;nNR1Vm?qaY`E4WT=u%gKhO0tt@D+!6? w2)W5elnk^?7!4VX8BH0DxF9iLWNKgmWtte8Btn^H<|ZZ^137snH|RG20E-5id;kCd delta 460 zcmZojXh@hKr<|0Qn3S57nyQnQUzD3zVr8h4mXlhNoRL~&WtL=OmX?y3w$bq}KQZby zI||(7H!#-8O{}moHZai1ORXreGS(?bOiwMgGSMl@&o8kuF#rK`W0O>!lFZyxD?{VS zUJAZ?1q@Fa85y{x3=F-^qCx^gqTGVajV(h0Bb`jcOd>)IU48i&Js3bB zTujxqK<;$LNlcS@6y