From 7634ecc74d2a15149a42e516ed38a358b5daec3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Mu=C3=B1oz?= Date: Fri, 4 Oct 2024 09:43:18 +0200 Subject: [PATCH 01/10] Fix format codegen files (#3695) --- .../codegen/decision/DecisionCodegen.java | 2 +- .../kogito/codegen/process/ProcessCodegen.java | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/kogito-codegen-modules/kogito-codegen-decisions/src/main/java/org/kie/kogito/codegen/decision/DecisionCodegen.java b/kogito-codegen-modules/kogito-codegen-decisions/src/main/java/org/kie/kogito/codegen/decision/DecisionCodegen.java index 757bd5caf04..de95c7c77e2 100644 --- a/kogito-codegen-modules/kogito-codegen-decisions/src/main/java/org/kie/kogito/codegen/decision/DecisionCodegen.java +++ b/kogito-codegen-modules/kogito-codegen-decisions/src/main/java/org/kie/kogito/codegen/decision/DecisionCodegen.java @@ -73,7 +73,7 @@ public class DecisionCodegen extends AbstractGenerator { public static final String GENERATOR_NAME = "decisions"; /** - * (boolean) generate java classes to support strongly typed input (default false) + * (boolean) generate java classes to support strongly typed input (default false) */ public static String STRONGLY_TYPED_CONFIGURATION_KEY = "kogito.decisions.stronglytyped"; /** diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java index c360ec5e8c2..71debb2806b 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java @@ -356,17 +356,17 @@ protected Collection internalGenerate() { //Creating and adding the ResourceGenerator for REST generation if (context().hasRest()) { ProcessResourceGenerator processResourceGenerator = new ProcessResourceGenerator( - context(), - workFlowProcess, - modelClassGenerator.className(), - execModelGen.className(), - applicationCanonicalName()); + context(), + workFlowProcess, + modelClassGenerator.className(), + execModelGen.className(), + applicationCanonicalName()); processResourceGenerator - .withUserTasks(processIdToUserTaskModel.get(workFlowProcess.getId())) - .withSignals(metaData.getSignals()) - .withTriggers(metaData.isStartable(), metaData.isDynamic(), metaData.getTriggers()) - .withTransaction(isTransactionEnabled()); + .withUserTasks(processIdToUserTaskModel.get(workFlowProcess.getId())) + .withSignals(metaData.getSignals()) + .withTriggers(metaData.isStartable(), metaData.isDynamic(), metaData.getTriggers()) + .withTransaction(isTransactionEnabled()); rgs.add(processResourceGenerator); } From adb145ded1c0ded1bdcb81427a7532d26ba515d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pere=20Fern=C3=A1ndez?= Date: Fri, 4 Oct 2024 15:56:15 +0200 Subject: [PATCH 02/10] NO_ISSUE: Copying resources directly to `target` instead to `src/main/resources` to keep module sources clean. (#3691) * NO_ISSUE: Copying resources directly to `target` instead to `src/main/resources` to keep module sources clean. * - fix formatting * - renamed path variables * - format --- .../common/persistence/postgresql/.gitignore | 20 --------------- addons/common/persistence/postgresql/pom.xml | 25 ++++++------------- 2 files changed, 8 insertions(+), 37 deletions(-) delete mode 100644 addons/common/persistence/postgresql/.gitignore diff --git a/addons/common/persistence/postgresql/.gitignore b/addons/common/persistence/postgresql/.gitignore deleted file mode 100644 index 423a2b902f1..00000000000 --- a/addons/common/persistence/postgresql/.gitignore +++ /dev/null @@ -1,20 +0,0 @@ -### -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -### - -/src/main/resources/kie-flyway/db/persistence-postgresql/postgresql/* \ No newline at end of file diff --git a/addons/common/persistence/postgresql/pom.xml b/addons/common/persistence/postgresql/pom.xml index 581d6edf9f2..4b3048dbdba 100644 --- a/addons/common/persistence/postgresql/pom.xml +++ b/addons/common/persistence/postgresql/pom.xml @@ -1,4 +1,4 @@ - + org.apache.maven.plugins maven-resources-plugin @@ -139,10 +130,10 @@ copy-resources - ${path.to.migration.scripts.to} + ${path.to.migration.scripts.target} - ${path.to.migration.scripts.from} + ${path.to.migration.scripts.source} From b81ff21b9e359d23b7c588b9d3b042cc0043db9e Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti <65240126+fjtirado@users.noreply.github.com> Date: Fri, 4 Oct 2024 17:44:55 +0200 Subject: [PATCH 03/10] [incubator-kie-issues#1457] Allow grouping of events (#3654) * [Fix apache/incubator-kie-issues#1457] Allow grouping of events * Add test for coverage * [Fix apache/incubator-kie-issues#1457] Grouping of event serialization * [Fix apache/incubator-kie-issues#1457] Adding default constructor * [Fix apache/incubator-kie-issues#1457] Adding TYPE constant --------- Co-authored-by: gmunozfe --- .../kie/kogito/event/AbstractDataEvent.java | 16 +- .../MultipleProcessInstanceDataEvent.java | 34 ++ .../process/ProcessInstanceDataEvent.java | 6 + .../MultipleUserTaskInstanceDataEvent.java | 34 ++ .../usertask/UserTaskInstanceDataEvent.java | 5 + kogito-build/kogito-dependencies-bom/pom.xml | 2 +- quarkus/addons/events/process/runtime/pom.xml | 11 + .../AbstractMessagingEventPublisher.java | 188 +++++++++ .../GroupingMessagingEventPublisher.java | 72 ++++ .../ReactiveMessagingEventPublisher.java | 135 +----- .../GroupingMessagingEventPublisherTest.java | 392 ++++++++++++++++++ 11 files changed, 757 insertions(+), 138 deletions(-) create mode 100644 api/kogito-events-core/src/main/java/org/kie/kogito/event/process/MultipleProcessInstanceDataEvent.java create mode 100644 api/kogito-events-core/src/main/java/org/kie/kogito/event/usertask/MultipleUserTaskInstanceDataEvent.java create mode 100644 quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/AbstractMessagingEventPublisher.java create mode 100644 quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/GroupingMessagingEventPublisher.java create mode 100644 quarkus/addons/events/process/runtime/src/test/java/org/kie/kogito/events/process/GroupingMessagingEventPublisherTest.java diff --git a/api/kogito-events-core/src/main/java/org/kie/kogito/event/AbstractDataEvent.java b/api/kogito-events-core/src/main/java/org/kie/kogito/event/AbstractDataEvent.java index 2fcdd704196..065111c8519 100644 --- a/api/kogito-events-core/src/main/java/org/kie/kogito/event/AbstractDataEvent.java +++ b/api/kogito-events-core/src/main/java/org/kie/kogito/event/AbstractDataEvent.java @@ -177,6 +177,15 @@ public abstract class AbstractDataEvent implements DataEvent { protected AbstractDataEvent() { } + protected AbstractDataEvent(String type, URI source, T body) { + this.specVersion = SpecVersion.parse(SPEC_VERSION); + this.id = UUID.randomUUID().toString(); + this.source = source; + this.type = type; + this.time = ZonedDateTime.now().toOffsetDateTime(); + this.data = body; + } + protected AbstractDataEvent(String type, String source, T body, @@ -201,12 +210,7 @@ protected AbstractDataEvent(String type, String subject, String dataContentType, String dataSchema) { - this.specVersion = SpecVersion.parse(SPEC_VERSION); - this.id = UUID.randomUUID().toString(); - this.source = Optional.ofNullable(source).map(URI::create).orElse(null); - this.type = type; - this.time = ZonedDateTime.now().toOffsetDateTime(); - this.data = body; + this(type, Optional.ofNullable(source).map(URI::create).orElse(null), body); setKogitoProcessInstanceId(kogitoProcessInstanceId); setKogitoRootProcessInstanceId(kogitoRootProcessInstanceId); setKogitoProcessId(kogitoProcessId); diff --git a/api/kogito-events-core/src/main/java/org/kie/kogito/event/process/MultipleProcessInstanceDataEvent.java b/api/kogito-events-core/src/main/java/org/kie/kogito/event/process/MultipleProcessInstanceDataEvent.java new file mode 100644 index 00000000000..7db8c0e7659 --- /dev/null +++ b/api/kogito-events-core/src/main/java/org/kie/kogito/event/process/MultipleProcessInstanceDataEvent.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.event.process; + +import java.net.URI; +import java.util.Collection; + +public class MultipleProcessInstanceDataEvent extends ProcessInstanceDataEvent>> { + + public static final String TYPE = "MultipleProcessInstanceDataEvent"; + + public MultipleProcessInstanceDataEvent() { + } + + public MultipleProcessInstanceDataEvent(URI source, Collection> body) { + super(TYPE, source, body); + } +} diff --git a/api/kogito-events-core/src/main/java/org/kie/kogito/event/process/ProcessInstanceDataEvent.java b/api/kogito-events-core/src/main/java/org/kie/kogito/event/process/ProcessInstanceDataEvent.java index 8069df7ee39..31131563dcc 100644 --- a/api/kogito-events-core/src/main/java/org/kie/kogito/event/process/ProcessInstanceDataEvent.java +++ b/api/kogito-events-core/src/main/java/org/kie/kogito/event/process/ProcessInstanceDataEvent.java @@ -18,6 +18,8 @@ */ package org.kie.kogito.event.process; +import java.net.URI; + import org.kie.kogito.event.AbstractDataEvent; public class ProcessInstanceDataEvent extends AbstractDataEvent { @@ -29,6 +31,10 @@ public ProcessInstanceDataEvent(T body) { setData(body); } + protected ProcessInstanceDataEvent(String type, URI source, T body) { + super(type, source, body); + } + public ProcessInstanceDataEvent(String type, String source, T body, diff --git a/api/kogito-events-core/src/main/java/org/kie/kogito/event/usertask/MultipleUserTaskInstanceDataEvent.java b/api/kogito-events-core/src/main/java/org/kie/kogito/event/usertask/MultipleUserTaskInstanceDataEvent.java new file mode 100644 index 00000000000..b2b62c61d83 --- /dev/null +++ b/api/kogito-events-core/src/main/java/org/kie/kogito/event/usertask/MultipleUserTaskInstanceDataEvent.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.event.usertask; + +import java.net.URI; +import java.util.Collection; + +public class MultipleUserTaskInstanceDataEvent extends UserTaskInstanceDataEvent>> { + + public static final String TYPE = "MultipleUserTaskInstanceDataEvent"; + + public MultipleUserTaskInstanceDataEvent() { + } + + public MultipleUserTaskInstanceDataEvent(URI source, Collection> body) { + super(TYPE, source, body); + } +} diff --git a/api/kogito-events-core/src/main/java/org/kie/kogito/event/usertask/UserTaskInstanceDataEvent.java b/api/kogito-events-core/src/main/java/org/kie/kogito/event/usertask/UserTaskInstanceDataEvent.java index 98fc6528094..c4b3e0af5c9 100644 --- a/api/kogito-events-core/src/main/java/org/kie/kogito/event/usertask/UserTaskInstanceDataEvent.java +++ b/api/kogito-events-core/src/main/java/org/kie/kogito/event/usertask/UserTaskInstanceDataEvent.java @@ -18,6 +18,7 @@ */ package org.kie.kogito.event.usertask; +import java.net.URI; import java.util.Set; import org.kie.kogito.event.AbstractDataEvent; @@ -48,6 +49,10 @@ public UserTaskInstanceDataEvent(T body) { setData(body); } + protected UserTaskInstanceDataEvent(String type, URI source, T body) { + super(type, source, body); + } + public UserTaskInstanceDataEvent(String type, String source, T body, diff --git a/kogito-build/kogito-dependencies-bom/pom.xml b/kogito-build/kogito-dependencies-bom/pom.xml index 0c815fc2353..26fae00aa0d 100644 --- a/kogito-build/kogito-dependencies-bom/pom.xml +++ b/kogito-build/kogito-dependencies-bom/pom.xml @@ -52,7 +52,7 @@ 2.0.2 2.4.1 0.3.0 - 2.2.0 + 2.4.1 0.2.3 1.5.2 3.25.8 diff --git a/quarkus/addons/events/process/runtime/pom.xml b/quarkus/addons/events/process/runtime/pom.xml index dc5d417218d..50ef92684c1 100644 --- a/quarkus/addons/events/process/runtime/pom.xml +++ b/quarkus/addons/events/process/runtime/pom.xml @@ -78,6 +78,17 @@ org.slf4j slf4j-api + + + org.junit.jupiter + junit-jupiter + test + + + io.quarkus + quarkus-junit5-mockito + test + diff --git a/quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/AbstractMessagingEventPublisher.java b/quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/AbstractMessagingEventPublisher.java new file mode 100644 index 00000000000..f8092c83575 --- /dev/null +++ b/quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/AbstractMessagingEventPublisher.java @@ -0,0 +1,188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.events.process; + +import java.util.Collection; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.function.Consumer; + +import org.eclipse.microprofile.reactive.messaging.Channel; +import org.eclipse.microprofile.reactive.messaging.Message; +import org.eclipse.microprofile.reactive.messaging.OnOverflow; +import org.eclipse.microprofile.reactive.messaging.OnOverflow.Strategy; +import org.kie.kogito.addon.quarkus.common.reactive.messaging.MessageDecoratorProvider; +import org.kie.kogito.event.DataEvent; +import org.kie.kogito.event.EventPublisher; +import org.kie.kogito.events.config.EventsRuntimeConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.smallrye.reactive.messaging.MutinyEmitter; +import io.smallrye.reactive.messaging.providers.locals.ContextAwareMessage; + +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; + +public abstract class AbstractMessagingEventPublisher implements EventPublisher { + + private static final Logger logger = LoggerFactory.getLogger(AbstractMessagingEventPublisher.class); + + @Inject + ObjectMapper json; + + @Inject + @Channel(PROCESS_INSTANCES_TOPIC_NAME) + @OnOverflow(Strategy.UNBOUNDED_BUFFER) + MutinyEmitter processInstancesEventsEmitter; + private AbstractMessageEmitter processInstanceConsumer; + + @Inject + @Channel(PROCESS_DEFINITIONS_TOPIC_NAME) + MutinyEmitter processDefinitionEventsEmitter; + private AbstractMessageEmitter processDefinitionConsumer; + + @Inject + @Channel(USER_TASK_INSTANCES_TOPIC_NAME) + MutinyEmitter userTasksEventsEmitter; + private AbstractMessageEmitter userTaskConsumer; + @Inject + EventsRuntimeConfig eventsRuntimeConfig; + + @Inject + Instance decoratorProviderInstance; + + private MessageDecoratorProvider decoratorProvider; + + @PostConstruct + public void init() { + decoratorProvider = decoratorProviderInstance.isResolvable() ? decoratorProviderInstance.get() : null; + processDefinitionConsumer = eventsRuntimeConfig.isProcessInstancesPropagateError() ? new BlockingMessageEmitter(processDefinitionEventsEmitter, PROCESS_DEFINITIONS_TOPIC_NAME) + : new ReactiveMessageEmitter(processDefinitionEventsEmitter, PROCESS_DEFINITIONS_TOPIC_NAME); + processInstanceConsumer = eventsRuntimeConfig.isProcessDefinitionPropagateError() ? new BlockingMessageEmitter(processInstancesEventsEmitter, PROCESS_INSTANCES_TOPIC_NAME) + : new ReactiveMessageEmitter(processInstancesEventsEmitter, PROCESS_INSTANCES_TOPIC_NAME); + userTaskConsumer = eventsRuntimeConfig.isUserTasksPropagateError() ? new BlockingMessageEmitter(userTasksEventsEmitter, USER_TASK_INSTANCES_TOPIC_NAME) + : new ReactiveMessageEmitter(userTasksEventsEmitter, USER_TASK_INSTANCES_TOPIC_NAME); + } + + protected Optional getConsumer(DataEvent event) { + if (event == null) { + return Optional.empty(); + } + switch (event.getType()) { + case "ProcessDefinitionEvent": + return eventsRuntimeConfig.isProcessDefinitionEventsEnabled() ? Optional.of(processDefinitionConsumer) : Optional.empty(); + + case "ProcessInstanceErrorDataEvent": + case "ProcessInstanceNodeDataEvent": + case "ProcessInstanceSLADataEvent": + case "ProcessInstanceStateDataEvent": + case "ProcessInstanceVariableDataEvent": + return eventsRuntimeConfig.isProcessInstancesEventsEnabled() ? Optional.of(processInstanceConsumer) : Optional.empty(); + + case "UserTaskInstanceAssignmentDataEvent": + case "UserTaskInstanceAttachmentDataEvent": + case "UserTaskInstanceCommentDataEvent": + case "UserTaskInstanceDeadlineDataEvent": + case "UserTaskInstanceStateDataEvent": + case "UserTaskInstanceVariableDataEvent": + return eventsRuntimeConfig.isUserTasksEventsEnabled() ? Optional.of(userTaskConsumer) : Optional.empty(); + + default: + return Optional.empty(); + } + } + + @Override + public void publish(Collection> events) { + for (DataEvent event : events) { + publish(event); + } + } + + protected void publishToTopic(AbstractMessageEmitter emitter, Object event) { + logger.debug("About to publish event {} to topic {}", event, emitter.topic); + Message message = null; + try { + String eventString = json.writeValueAsString(event); + logger.debug("Event payload '{}'", eventString); + message = decorateMessage(ContextAwareMessage.of(eventString)); + } catch (Exception e) { + logger.error("Error while creating event to topic {} for event {}", emitter.topic, event); + } + if (message != null) { + emitter.accept(message); + } + } + + protected Message decorateMessage(Message message) { + return decoratorProvider != null ? decoratorProvider.decorate(message) : message; + } + + protected static abstract class AbstractMessageEmitter implements Consumer> { + + protected final String topic; + protected final MutinyEmitter emitter; + + protected AbstractMessageEmitter(MutinyEmitter emitter, String topic) { + this.emitter = emitter; + this.topic = topic; + } + } + + private static class BlockingMessageEmitter extends AbstractMessageEmitter { + protected BlockingMessageEmitter(MutinyEmitter emitter, String topic) { + super(emitter, topic); + } + + @Override + public void accept(Message message) { + emitter.sendMessageAndAwait(message); + logger.debug("Successfully published message {}", message.getPayload()); + } + } + + private static class ReactiveMessageEmitter extends AbstractMessageEmitter { + protected ReactiveMessageEmitter(MutinyEmitter emitter, String topic) { + super(emitter, topic); + } + + @Override + public void accept(Message message) { + emitter.sendMessageAndForget(message + .withAck(() -> onAck(message)) + .withNack(reason -> onNack(reason, message))); + } + + private CompletionStage onAck(Message message) { + logger.debug("Successfully published message {}", message.getPayload()); + return CompletableFuture.completedFuture(null); + } + + private CompletionStage onNack(Throwable reason, Message message) { + logger.error("Error while publishing message {}", message, reason); + return CompletableFuture.completedFuture(null); + } + + } +} diff --git a/quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/GroupingMessagingEventPublisher.java b/quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/GroupingMessagingEventPublisher.java new file mode 100644 index 00000000000..ee1f284b3a8 --- /dev/null +++ b/quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/GroupingMessagingEventPublisher.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.events.process; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.kie.kogito.event.DataEvent; +import org.kie.kogito.event.process.MultipleProcessInstanceDataEvent; +import org.kie.kogito.event.process.ProcessInstanceDataEvent; +import org.kie.kogito.event.usertask.MultipleUserTaskInstanceDataEvent; +import org.kie.kogito.event.usertask.UserTaskInstanceDataEvent; + +import io.quarkus.arc.properties.IfBuildProperty; + +import jakarta.inject.Singleton; + +@Singleton +@IfBuildProperty(name = "kogito.events.grouping", stringValue = "true") +public class GroupingMessagingEventPublisher extends AbstractMessagingEventPublisher { + + @Override + public void publish(DataEvent event) { + publish(Collections.singletonList(event)); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public void publish(Collection> events) { + Map eventsByChannel = new HashMap<>(); + for (DataEvent event : events) { + getConsumer(event).ifPresent(c -> eventsByChannel.computeIfAbsent(c, k -> new ArrayList<>()).add(event)); + } + eventsByChannel.entrySet().forEach(this::publishEvents); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private void publishEvents(Map.Entry entry) { + DataEvent firstEvent = (DataEvent) entry.getValue().iterator().next(); + URI source = firstEvent.getSource(); + if (firstEvent instanceof UserTaskInstanceDataEvent) { + publishToTopic(entry.getKey(), new MultipleUserTaskInstanceDataEvent(source, (Collection>) entry.getValue())); + } else if (firstEvent instanceof ProcessInstanceDataEvent) { + publishToTopic(entry.getKey(), new MultipleProcessInstanceDataEvent(source, (Collection>) entry.getValue())); + } else { + for (DataEvent event : (Collection>) entry.getValue()) { + publishToTopic(entry.getKey(), event); + } + } + } +} diff --git a/quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/ReactiveMessagingEventPublisher.java b/quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/ReactiveMessagingEventPublisher.java index c232a1eb471..c6aa4424f0c 100644 --- a/quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/ReactiveMessagingEventPublisher.java +++ b/quarkus/addons/events/process/runtime/src/main/java/org/kie/kogito/events/process/ReactiveMessagingEventPublisher.java @@ -19,102 +19,20 @@ package org.kie.kogito.events.process; import java.util.Collection; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.function.BiConsumer; -import org.eclipse.microprofile.reactive.messaging.Channel; -import org.eclipse.microprofile.reactive.messaging.Message; -import org.eclipse.microprofile.reactive.messaging.OnOverflow; -import org.eclipse.microprofile.reactive.messaging.OnOverflow.Strategy; -import org.kie.kogito.addon.quarkus.common.reactive.messaging.MessageDecoratorProvider; import org.kie.kogito.event.DataEvent; -import org.kie.kogito.event.EventPublisher; -import org.kie.kogito.events.config.EventsRuntimeConfig; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.arc.properties.UnlessBuildProperty; -import io.smallrye.reactive.messaging.MutinyEmitter; -import io.smallrye.reactive.messaging.providers.locals.ContextAwareMessage; - -import jakarta.annotation.PostConstruct; -import jakarta.enterprise.inject.Instance; -import jakarta.inject.Inject; import jakarta.inject.Singleton; @Singleton -public class ReactiveMessagingEventPublisher implements EventPublisher { - - private static final Logger logger = LoggerFactory.getLogger(ReactiveMessagingEventPublisher.class); - - @Inject - ObjectMapper json; - - @Inject - @Channel(PROCESS_INSTANCES_TOPIC_NAME) - @OnOverflow(Strategy.UNBOUNDED_BUFFER) - MutinyEmitter processInstancesEventsEmitter; - private BiConsumer, Message> processInstanceConsumer; - - @Inject - @Channel(PROCESS_DEFINITIONS_TOPIC_NAME) - MutinyEmitter processDefinitionEventsEmitter; - private BiConsumer, Message> processDefinitionConsumer; - - @Inject - @Channel(USER_TASK_INSTANCES_TOPIC_NAME) - MutinyEmitter userTasksEventsEmitter; - private BiConsumer, Message> userTaskConsumer; - @Inject - EventsRuntimeConfig eventsRuntimeConfig; - - @Inject - Instance decoratorProviderInstance; - - private MessageDecoratorProvider decoratorProvider; - - @PostConstruct - public void init() { - decoratorProvider = decoratorProviderInstance.isResolvable() ? decoratorProviderInstance.get() : null; - processInstanceConsumer = eventsRuntimeConfig.isProcessInstancesPropagateError() ? new BlockingMessageEmitter() : new ReactiveMessageEmitter(); - processDefinitionConsumer = eventsRuntimeConfig.isProcessDefinitionPropagateError() ? new BlockingMessageEmitter() : new ReactiveMessageEmitter(); - userTaskConsumer = eventsRuntimeConfig.isUserTasksPropagateError() ? new BlockingMessageEmitter() : new ReactiveMessageEmitter(); - } +@UnlessBuildProperty(name = "kogito.events.grouping", stringValue = "true", enableIfMissing = true) +public class ReactiveMessagingEventPublisher extends AbstractMessagingEventPublisher { @Override public void publish(DataEvent event) { - - switch (event.getType()) { - case "ProcessDefinitionEvent": - if (eventsRuntimeConfig.isProcessDefinitionEventsEnabled()) { - publishToTopic(processDefinitionConsumer, event, processDefinitionEventsEmitter, PROCESS_DEFINITIONS_TOPIC_NAME); - } - break; - case "ProcessInstanceErrorDataEvent": - case "ProcessInstanceNodeDataEvent": - case "ProcessInstanceSLADataEvent": - case "ProcessInstanceStateDataEvent": - case "ProcessInstanceVariableDataEvent": - if (eventsRuntimeConfig.isProcessInstancesEventsEnabled()) { - publishToTopic(processInstanceConsumer, event, processInstancesEventsEmitter, PROCESS_INSTANCES_TOPIC_NAME); - } - break; - - case "UserTaskInstanceAssignmentDataEvent": - case "UserTaskInstanceAttachmentDataEvent": - case "UserTaskInstanceCommentDataEvent": - case "UserTaskInstanceDeadlineDataEvent": - case "UserTaskInstanceStateDataEvent": - case "UserTaskInstanceVariableDataEvent": - if (eventsRuntimeConfig.isUserTasksEventsEnabled()) { - publishToTopic(userTaskConsumer, event, userTasksEventsEmitter, USER_TASK_INSTANCES_TOPIC_NAME); - } - break; - default: - logger.debug("Unknown type of event '{}', ignoring for this publisher", event.getType()); - } + getConsumer(event).ifPresent(emitter -> publishToTopic(emitter, event)); } @Override @@ -124,49 +42,4 @@ public void publish(Collection> events) { } } - protected void publishToTopic(BiConsumer, Message> consumer, DataEvent event, MutinyEmitter emitter, String topic) { - logger.debug("About to publish event {} to topic {}", event, topic); - Message message = null; - try { - String eventString = json.writeValueAsString(event); - logger.debug("Event payload '{}'", eventString); - message = decorateMessage(ContextAwareMessage.of(eventString)); - } catch (Exception e) { - logger.error("Error while creating event to topic {} for event {}", topic, event); - } - if (message != null) { - consumer.accept(emitter, message); - } - } - - protected CompletionStage onAck(Message message) { - logger.debug("Successfully published message {}", message.getPayload()); - return CompletableFuture.completedFuture(null); - } - - protected CompletionStage onNack(Throwable reason, Message message) { - logger.error("Error while publishing message {}", message, reason); - return CompletableFuture.completedFuture(null); - } - - protected Message decorateMessage(Message message) { - return decoratorProvider != null ? decoratorProvider.decorate(message) : message; - } - - private class BlockingMessageEmitter implements BiConsumer, Message> { - @Override - public void accept(MutinyEmitter emitter, Message message) { - emitter.sendMessageAndAwait(message); - logger.debug("Successfully published message {}", message.getPayload()); - } - } - - private class ReactiveMessageEmitter implements BiConsumer, Message> { - @Override - public void accept(MutinyEmitter emitter, Message message) { - emitter.sendMessageAndForget(message - .withAck(() -> onAck(message)) - .withNack(reason -> onNack(reason, message))); - } - } } diff --git a/quarkus/addons/events/process/runtime/src/test/java/org/kie/kogito/events/process/GroupingMessagingEventPublisherTest.java b/quarkus/addons/events/process/runtime/src/test/java/org/kie/kogito/events/process/GroupingMessagingEventPublisherTest.java new file mode 100644 index 00000000000..0d784d3a53b --- /dev/null +++ b/quarkus/addons/events/process/runtime/src/test/java/org/kie/kogito/events/process/GroupingMessagingEventPublisherTest.java @@ -0,0 +1,392 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.kie.kogito.events.process; + +import java.util.*; + +import org.eclipse.microprofile.reactive.messaging.Message; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.kie.kogito.addon.quarkus.common.reactive.messaging.MessageDecoratorProvider; +import org.kie.kogito.event.DataEvent; +import org.kie.kogito.event.process.MultipleProcessInstanceDataEvent; +import org.kie.kogito.event.process.ProcessInstanceDataEvent; +import org.kie.kogito.event.usertask.MultipleUserTaskInstanceDataEvent; +import org.kie.kogito.event.usertask.UserTaskInstanceDataEvent; +import org.kie.kogito.events.config.EventsRuntimeConfig; +import org.kie.kogito.events.process.AbstractMessagingEventPublisher.AbstractMessageEmitter; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.smallrye.reactive.messaging.MutinyEmitter; + +import jakarta.enterprise.inject.Instance; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +@SuppressWarnings("unchecked") +public class GroupingMessagingEventPublisherTest { + + @Mock + private ObjectMapper json; + + @Mock + private MutinyEmitter processInstancesEventsEmitter; + + @Mock + private MutinyEmitter processDefinitionEventsEmitter; + + @Mock + private MutinyEmitter userTasksEventsEmitter; + + @Mock + private EventsRuntimeConfig eventsRuntimeConfig; + + @Mock + private MessageDecoratorProvider decoratorProvider; + + @Mock + private Message decoratedMessage; + + @Mock + private Instance decoratorProviderInstance; + + @Mock + private AbstractMessagingEventPublisher.AbstractMessageEmitter processInstanceConsumer; + + @Mock + private AbstractMessagingEventPublisher.AbstractMessageEmitter userTaskConsumer; + + @Mock + private AbstractMessagingEventPublisher.AbstractMessageEmitter processDefinitionConsumer; + + @Spy + @InjectMocks + private GroupingMessagingEventPublisher groupingMessagingEventPublisher; + + @Spy + @InjectMocks + private ReactiveMessagingEventPublisher reactiveMessagingEventPublisher; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + + when(decoratorProviderInstance.isResolvable()).thenReturn(true); + when(decoratorProviderInstance.get()).thenReturn(decoratorProvider); + + when(eventsRuntimeConfig.isProcessInstancesPropagateError()).thenReturn(false); + when(eventsRuntimeConfig.isProcessDefinitionPropagateError()).thenReturn(false); + when(eventsRuntimeConfig.isUserTasksPropagateError()).thenReturn(false); + + when(eventsRuntimeConfig.isProcessInstancesEventsEnabled()).thenReturn(true); + when(eventsRuntimeConfig.isUserTasksEventsEnabled()).thenReturn(true); + } + + @Test + public void testGroupingMessagingEventPublisher_publish() throws Exception { + DataEvent event = mock(DataEvent.class); + when(event.getType()).thenReturn("ProcessInstanceErrorDataEvent"); + + // Test initialization + groupingMessagingEventPublisher.init(); + when(decoratorProvider.decorate(any(Message.class))).thenReturn(decoratedMessage); + + // Mock the message behavior + mockMessageForBothAckNack(decoratedMessage); + + // Call method + groupingMessagingEventPublisher.publish(event); + + // Verify that the consumer has been invoked + verify(processInstancesEventsEmitter).sendMessageAndForget(any()); + } + + @Test + public void testReactiveMessagingEventPublisher_publish() throws Exception { + DataEvent event = mock(DataEvent.class); + when(event.getType()).thenReturn("ProcessInstanceErrorDataEvent"); + + // Test initialization + reactiveMessagingEventPublisher.init(); + when(decoratorProvider.decorate(any(Message.class))).thenReturn(decoratedMessage); + + // Mock the message behavior + mockMessageForBothAckNack(decoratedMessage); + + // Call method + reactiveMessagingEventPublisher.publish(event); + + // Verify that the consumer has been invoked + verify(processInstancesEventsEmitter).sendMessageAndForget(any()); + } + + @Test + public void testPublishGroupingByChannel() { + // Create mock events + DataEvent processInstanceEvent = mock(ProcessInstanceDataEvent.class); + when(processInstanceEvent.getType()).thenReturn("ProcessInstanceStateDataEvent"); + + DataEvent userTaskEvent = mock(UserTaskInstanceDataEvent.class); + when(userTaskEvent.getType()).thenReturn("UserTaskInstanceStateDataEvent"); + + // Mock getConsumer() to return different emitters based on event type + doReturn(Optional.of(processInstanceConsumer)).when(groupingMessagingEventPublisher).getConsumer(processInstanceEvent); + doReturn(Optional.of(userTaskConsumer)).when(groupingMessagingEventPublisher).getConsumer(userTaskEvent); + + // Create a collection of events with different types (ProcessInstance and UserTask) + Collection> events = Arrays.asList(processInstanceEvent, userTaskEvent); + + // Spy on the publisher's internal method to verify the calls + doNothing().when(groupingMessagingEventPublisher).publishToTopic(any(), any()); + + // Invoke the method to test + groupingMessagingEventPublisher.publish(events); + + // Capture and verify that the correct emitter was used for each event + verify(groupingMessagingEventPublisher, times(1)).publishToTopic(eq(processInstanceConsumer), any(MultipleProcessInstanceDataEvent.class)); + verify(groupingMessagingEventPublisher, times(1)).publishToTopic(eq(userTaskConsumer), any(MultipleUserTaskInstanceDataEvent.class)); + } + + @Test + public void testPublishMultipleEventsGroupedByChannel() { + // Create multiple events of different types + DataEvent processInstanceEvent1 = mock(ProcessInstanceDataEvent.class); + DataEvent processInstanceEvent2 = mock(ProcessInstanceDataEvent.class); + DataEvent userTaskEvent1 = mock(UserTaskInstanceDataEvent.class); + DataEvent userTaskEvent2 = mock(UserTaskInstanceDataEvent.class); + + when(processInstanceEvent1.getType()).thenReturn("ProcessInstanceStateDataEvent"); + when(processInstanceEvent2.getType()).thenReturn("ProcessInstanceStateDataEvent"); + when(userTaskEvent1.getType()).thenReturn("UserTaskInstanceStateDataEvent"); + when(userTaskEvent2.getType()).thenReturn("UserTaskInstanceStateDataEvent"); + + // Mock getConsumer() to return corresponding emitters for event types + doReturn(Optional.of(processInstanceConsumer)).when(groupingMessagingEventPublisher).getConsumer(processInstanceEvent1); + doReturn(Optional.of(processInstanceConsumer)).when(groupingMessagingEventPublisher).getConsumer(processInstanceEvent2); + doReturn(Optional.of(userTaskConsumer)).when(groupingMessagingEventPublisher).getConsumer(userTaskEvent1); + doReturn(Optional.of(userTaskConsumer)).when(groupingMessagingEventPublisher).getConsumer(userTaskEvent2); + + // Create a collection of events that would be grouped by channel + Collection> events = Arrays.asList(processInstanceEvent1, processInstanceEvent2, userTaskEvent1, userTaskEvent2); + + // Spy on the internal publishToTopic to verify grouping + doNothing().when(groupingMessagingEventPublisher).publishToTopic(any(), any()); + + // Invoke the method to test + groupingMessagingEventPublisher.publish(events); + + // Verify that two grouped publishToTopic calls are made: one for processInstanceConsumer, one for userTaskConsumer + verify(groupingMessagingEventPublisher, times(1)).publishToTopic(eq(processInstanceConsumer), any(MultipleProcessInstanceDataEvent.class)); + verify(groupingMessagingEventPublisher, times(1)).publishToTopic(eq(userTaskConsumer), any(MultipleUserTaskInstanceDataEvent.class)); + + // Verify that the right number of events was grouped and passed to each emitter + ArgumentCaptor captorPI = ArgumentCaptor.forClass(MultipleProcessInstanceDataEvent.class); + + verify(groupingMessagingEventPublisher, times(1)).publishToTopic(eq(processInstanceConsumer), captorPI.capture()); + MultipleProcessInstanceDataEvent groupedProcessInstanceEvents = captorPI.getValue(); + assertEquals(2, groupedProcessInstanceEvents.getData().size()); // both processInstanceEvents are grouped + + ArgumentCaptor captorUT = ArgumentCaptor.forClass(MultipleUserTaskInstanceDataEvent.class); + + verify(groupingMessagingEventPublisher, times(1)).publishToTopic(eq(userTaskConsumer), captorUT.capture()); + MultipleUserTaskInstanceDataEvent groupedUserTaskEvents = captorUT.getValue(); + assertEquals(2, groupedUserTaskEvents.getData().size()); // both userTaskEvents are grouped + } + + @Test + public void testPublishEmptyEventsCollection() { + Collection> events = Collections.emptyList(); + + // Spy on the internal publishToTopic to verify no calls are made + doNothing().when(groupingMessagingEventPublisher).publishToTopic(any(), any()); + + groupingMessagingEventPublisher.publish(events); + + // Verify that publishToTopic is never called + verify(groupingMessagingEventPublisher, never()).publishToTopic(any(), anyCollection()); + } + + @Test + public void testNoConsumersFound() { + DataEvent processInstanceEvent = mock(DataEvent.class); + when(processInstanceEvent.getType()).thenReturn("ProcessInstanceStateDataEvent"); + + DataEvent userTaskEvent = mock(DataEvent.class); + when(userTaskEvent.getType()).thenReturn("UserTaskInstanceStateDataEvent"); + + // Mock getConsumer() to return empty optionals (no consumers found) + doReturn(Optional.empty()).when(groupingMessagingEventPublisher).getConsumer(processInstanceEvent); + doReturn(Optional.empty()).when(groupingMessagingEventPublisher).getConsumer(userTaskEvent); + + // Create a collection of events + Collection> events = Arrays.asList(processInstanceEvent, userTaskEvent); + + // Spy on the publisher's internal method to verify no calls are made + doNothing().when(groupingMessagingEventPublisher).publishToTopic(any(), any()); + + // Invoke the method to test + groupingMessagingEventPublisher.publish(events); + + // Verify that publishToTopic is never called since no consumers were found + verify(groupingMessagingEventPublisher, never()).publishToTopic(any(), anyCollection()); + } + + @Test + void testPublishToTopic_ExceptionHandling() throws Exception { + DataEvent event = mock(DataEvent.class); + when(event.getType()).thenReturn("ProcessInstanceErrorDataEvent"); + + groupingMessagingEventPublisher.init(); + when(decoratorProvider.decorate(any(Message.class))).thenThrow(new RuntimeException("Serialization error")); + + // Mock the message behavior + mockMessageForBothAckNack(decoratedMessage); + + // Call method + groupingMessagingEventPublisher.publish(event); + + // Check that emitter.sendMessageAndForget was never called + verify(processInstancesEventsEmitter, never()).sendMessageAndForget(any()); + } + + @Test + public void testPublishUnsupportedEventType() { + DataEvent unsupportedEvent = mock(DataEvent.class); + when(unsupportedEvent.getType()).thenReturn("UnsupportedEvent"); + + doReturn(Optional.empty()).when(groupingMessagingEventPublisher).getConsumer(unsupportedEvent); + + Collection> events = Collections.singletonList(unsupportedEvent); + + groupingMessagingEventPublisher.publish(events); + + // Verify no publishing occurred since no consumer exists for unsupported event + verify(groupingMessagingEventPublisher, never()).publishToTopic(any(), anyCollection()); + } + + @Test + public void testEventsDisabledInConfig() { + DataEvent processInstanceEvent = mock(DataEvent.class); + when(processInstanceEvent.getType()).thenReturn("ProcessInstanceStateDataEvent"); + + DataEvent userTaskEvent = mock(DataEvent.class); + when(userTaskEvent.getType()).thenReturn("UserTaskInstanceStateDataEvent"); + + // Disable process and user task events in the config + when(eventsRuntimeConfig.isProcessInstancesEventsEnabled()).thenReturn(false); + when(eventsRuntimeConfig.isUserTasksEventsEnabled()).thenReturn(false); + + Collection> events = Arrays.asList(processInstanceEvent, userTaskEvent); + + groupingMessagingEventPublisher.publish(events); + + // Verify no publishing occurred since events are disabled + verify(groupingMessagingEventPublisher, never()).publishToTopic(any(), anyCollection()); + } + + @Test + public void testNullEventInCollection() { + DataEvent validEvent = mock(ProcessInstanceDataEvent.class); + when(validEvent.getType()).thenReturn("ProcessInstanceStateDataEvent"); + + Collection> events = Arrays.asList(validEvent, null); // One valid event and one null event + + // Return a mock consumer for the valid event + doReturn(Optional.of(processInstanceConsumer)).when(groupingMessagingEventPublisher).getConsumer(validEvent); + + // Call the method + groupingMessagingEventPublisher.publish(events); + + // Verify the valid event is processed + verify(groupingMessagingEventPublisher, times(1)).publishToTopic(eq(processInstanceConsumer), any(MultipleProcessInstanceDataEvent.class)); + } + + @Test + public void testDecorateMessage() { + Message rawMessage = mock(Message.class); + when(decoratorProvider.decorate(rawMessage)).thenReturn(decoratedMessage); + + reactiveMessagingEventPublisher.init(); + + Message result = reactiveMessagingEventPublisher.decorateMessage(rawMessage); + assertEquals(decoratedMessage, result); + + verify(decoratorProvider).decorate(rawMessage); + } + + @Test + public void testPublishToTopicWithDecorator() throws Exception { + Object event = new Object(); + when(json.writeValueAsString(event)).thenReturn("eventString"); + + reactiveMessagingEventPublisher.init(); + + // Mock the message emitter + AbstractMessagingEventPublisher.AbstractMessageEmitter mockEmitter = mock(AbstractMessagingEventPublisher.AbstractMessageEmitter.class); + + // Ensure decorated message is used + when(decoratorProvider.decorate(any(Message.class))).thenReturn(decoratedMessage); + + // Spy on the reactiveMessagingEventPublisher to allow publishToTopic + reactiveMessagingEventPublisher.publishToTopic(mockEmitter, event); + + // Verify that the message was decorated and sent + verify(decoratorProvider).decorate(any(Message.class)); + verify(mockEmitter).accept(decoratedMessage); + } + + @Test + public void testPublishWithMultipleEventTypesSomeWithoutConsumers() { + DataEvent processInstanceEvent = mock(ProcessInstanceDataEvent.class); + when(processInstanceEvent.getType()).thenReturn("ProcessInstanceStateDataEvent"); + + DataEvent unsupportedEvent = mock(DataEvent.class); + when(unsupportedEvent.getType()).thenReturn("UnsupportedEvent"); + + doReturn(Optional.of(processInstanceConsumer)).when(groupingMessagingEventPublisher).getConsumer(processInstanceEvent); + doReturn(Optional.empty()).when(groupingMessagingEventPublisher).getConsumer(unsupportedEvent); + + Collection> events = Arrays.asList(processInstanceEvent, unsupportedEvent); + + groupingMessagingEventPublisher.publish(events); + + // Ensure that only the supported event was published + verify(groupingMessagingEventPublisher, times(1)).publishToTopic(eq(processInstanceConsumer), any(MultipleProcessInstanceDataEvent.class)); + verify(groupingMessagingEventPublisher, never()).publishToTopic(any(), eq(Collections.singletonList(unsupportedEvent))); + } + + private void mockMessageForBothAckNack(Message message) { + when(message.withAck(any())).thenReturn(message); + when(message.withNack(any())).thenReturn(message); + } +} From c05d52d12a41ef905a2b8ecb760e332acb2dd35d Mon Sep 17 00:00:00 2001 From: Gabriele Cardosi Date: Mon, 7 Oct 2024 13:48:38 +0200 Subject: [PATCH 04/10] [NO_ISSUE] Cleanup incubator-kie-issues#1504 - Conditionally build all or only reproducible modules based on only.reproducible flag (#3693) Co-authored-by: Gabriele-Cardosi --- jbpm/pom.xml | 41 ++++++++++++----------------------------- springboot/pom.xml | 29 ++++++++--------------------- 2 files changed, 20 insertions(+), 50 deletions(-) diff --git a/jbpm/pom.xml b/jbpm/pom.xml index b5638d6be7b..b53bf127c7c 100755 --- a/jbpm/pom.xml +++ b/jbpm/pom.xml @@ -39,6 +39,18 @@ jBPM: a Business Process Management http://www.jbpm.org + + jbpm-flow + jbpm-flow-builder + jbpm-bpmn2 + jbpm-flow-migration + process-serialization-protobuf + process-workitems + jbpm-usertask + jbpm-tools + jbpm-deps-groups + + allSubmodules @@ -48,36 +60,7 @@ - jbpm-flow - jbpm-flow-builder - jbpm-bpmn2 - jbpm-flow-migration - process-serialization-protobuf - process-workitems - jbpm-usertask - jbpm-tools jbpm-tests - jbpm-deps-groups - - - - - onlyReproducible - - - only.reproducible - - - - jbpm-flow - jbpm-flow-builder - jbpm-bpmn2 - jbpm-flow-migration - process-serialization-protobuf - process-workitems - jbpm-usertask - jbpm-tools - jbpm-deps-groups diff --git a/springboot/pom.xml b/springboot/pom.xml index 0e7a0f42316..3735936fe29 100644 --- a/springboot/pom.xml +++ b/springboot/pom.xml @@ -34,6 +34,14 @@ Kogito :: Spring Boot pom + + bom + addons + starters + archetype + test + + allSubmodules @@ -43,30 +51,9 @@ - bom - addons - starters - archetype - test integration-tests - - - onlyReproducible - - - only.reproducible - - - - bom - addons - starters - archetype - test - - \ No newline at end of file From 89a3f8282bf5ea0ea12ba69fd1d3f1fd7274dcb2 Mon Sep 17 00:00:00 2001 From: Enrique Date: Mon, 7 Oct 2024 20:04:03 +0200 Subject: [PATCH 05/10] [incubator-kie-issues-1484] Create endpoints for user task (#3673) --- .../KogitoWorkItemHandlerFactory.java | 2 +- .../event/KogitoUserTaskEventSupport.java | 3 +- .../process/workitem/TaskMetaEntity.java | 4 + .../org/kie/kogito/usertask/UserTask.java | 2 + .../usertask/UserTaskAssignmentStrategy.java | 35 ++ .../UserTaskAssignmentStrategyConfig.java | 32 ++ .../kie/kogito/usertask/UserTaskConfig.java | 4 + .../kie/kogito/usertask/UserTaskInstance.java | 43 +- .../kogito/usertask/UserTaskInstances.java | 13 +- .../kie/kogito/usertask/UserTaskService.java | 64 +++ .../org/kie/kogito/usertask/UserTasks.java | 2 + .../usertask/events/UserTaskStateEvent.java | 6 +- .../usertask/lifecycle/UserTaskLifeCycle.java | 6 +- .../usertask/lifecycle/UserTaskState.java | 7 +- .../UserTaskTransitionException.java | 29 ++ .../lifecycle/UserTaskTransitionExecutor.java | 3 +- .../lifecycle/UserTaskTransitionToken.java | 6 +- .../kie/kogito/usertask/model/Attachment.java | 4 + .../kie/kogito/usertask/model/Comment.java | 3 + .../kogito/usertask/model/CommentInfo.java | 40 ++ .../usertask/view/UserTaskTransitionView.java | 53 ++ .../kogito/usertask/view/UserTaskView.java | 178 +++++++ .../event/impl/DefaultInstanceEventBatch.java | 2 +- .../UserTaskStateEventDataEventAdapter.java | 4 +- .../canonical/AbstractNodeVisitor.java | 3 +- .../ProcessToExecModelGenerator.java | 17 +- ...taData.java => WorkItemModelMetaData.java} | 45 +- .../instance/LightWorkItemManager.java | 23 +- .../impl/WorkflowProcessInstanceImpl.java | 12 + .../instance/node/WorkItemNodeInstance.java | 4 +- .../process/impl/AbstractProcessInstance.java | 33 +- .../UserTaskKogitoWorkItemHandler.java | 186 ++----- ...kKogitoWorkItemHandlerProcessListener.java | 66 +++ .../usertask/impl/AbstractUserTask.java | 248 +++++++++ .../impl/BasicUserTaskAssignmentStrategy.java | 46 ++ .../kogito/usertask/impl/DefaultUserTask.java | 265 +--------- ...faultUserTaskAssignmentStrategyConfig.java | 54 ++ .../usertask/impl/DefaultUserTaskConfig.java | 30 +- .../DefaultUserTaskEventListenerConfig.java | 7 +- .../impl/DefaultUserTaskInstance.java | 188 ++++--- .../usertask/impl/DefaultUserTasks.java | 34 ++ .../impl/InMemoryUserTaskInstances.java | 79 ++- .../impl/KogitoUserTaskEventSupportImpl.java | 3 +- .../usertask/impl/UserTaskServiceImpl.java | 265 ++++++++++ .../impl/events/UserTaskStateEventImpl.java | 17 +- .../lifecycle/DefaultUserTaskLifeCycle.java | 153 +++++- .../DefaultUserTaskTransitionToken.java | 22 +- .../impl/DefaultKogitoWorkItemHandler.java | 28 +- .../kogito/codegen/tests/PublishEventIT.java | 22 +- .../kie/kogito/codegen/tests/UserTaskIT.java | 478 ++++++++---------- .../codegen/process/ProcessCodegen.java | 27 +- .../process/ProcessResourceGenerator.java | 44 +- ....java => WorkItemModelClassGenerator.java} | 12 +- .../codegen/usertask/UserTaskCodegen.java | 53 +- .../usertask/UserTaskConfigGenerator.java | 3 - .../RestResourceUserTaskQuarkusTemplate.java | 266 ---------- .../RestResourceUserTaskSpringTemplate.java | 259 ---------- .../RestResourceWorkItemQuarkusTemplate.java | 139 +++++ .../RestResourceWorkItemSpringTemplate.java | 137 +++++ .../RestResourceUserTaskQuarkusTemplate.java | 248 +++++++++ .../RestResourceUserTaskSpringTemplate.java | 210 ++++++++ .../UserTaskConfigQuarkusTemplate.java | 20 +- .../UserTaskConfigSpringTemplate.java | 17 +- .../usertask/UserTaskQuarkusTemplate.java | 16 +- .../usertask/UserTaskSpringTemplate.java | 17 +- .../UserTasksContainerQuarkusTemplate.java | 46 ++ .../UserTasksContainerSpringTemplate.java | 53 +- ...erTasksServiceProducerQuarkusTemplate.java | 68 +++ ...serTasksServiceProducerSpringTemplate.java | 67 +++ .../org/kie/kogito/it/PersistenceTest.java | 2 +- .../src/main/resources/AddedTask.bpmn2 | 231 +++++++++ .../kogito/it/InfinispanPersistenceIT.java | 75 +++ .../quarkus/AdHocFragmentsIT.java | 2 +- .../integrationtests/quarkus/TaskIT.java | 97 ++-- .../springboot/AdHocFragmentsTest.java | 2 +- .../integrationtests/springboot/TaskTest.java | 55 +- .../org/kie/kogito/it/PersistenceTest.java | 27 +- 77 files changed, 3486 insertions(+), 1580 deletions(-) create mode 100644 api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskAssignmentStrategy.java create mode 100644 api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskAssignmentStrategyConfig.java create mode 100644 api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskService.java create mode 100644 api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionException.java create mode 100644 api/kogito-api/src/main/java/org/kie/kogito/usertask/model/CommentInfo.java create mode 100644 api/kogito-api/src/main/java/org/kie/kogito/usertask/view/UserTaskTransitionView.java create mode 100644 api/kogito-api/src/main/java/org/kie/kogito/usertask/view/UserTaskView.java rename jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/{UserTaskModelMetaData.java => WorkItemModelMetaData.java} (90%) create mode 100644 jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandlerProcessListener.java create mode 100644 jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/AbstractUserTask.java create mode 100644 jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/BasicUserTaskAssignmentStrategy.java create mode 100644 jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskAssignmentStrategyConfig.java create mode 100644 jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/UserTaskServiceImpl.java rename kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/{UserTasksModelClassGenerator.java => WorkItemModelClassGenerator.java} (81%) delete mode 100644 kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceUserTaskQuarkusTemplate.java delete mode 100644 kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceUserTaskSpringTemplate.java create mode 100644 kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceWorkItemQuarkusTemplate.java create mode 100644 kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceWorkItemSpringTemplate.java create mode 100644 kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/RestResourceUserTaskQuarkusTemplate.java create mode 100644 kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/RestResourceUserTaskSpringTemplate.java create mode 100644 kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksServiceProducerQuarkusTemplate.java create mode 100644 kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksServiceProducerSpringTemplate.java create mode 100644 quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-quarkus-processes-infinispan/src/main/resources/AddedTask.bpmn2 diff --git a/api/kogito-api/src/main/java/org/kie/kogito/internal/process/workitem/KogitoWorkItemHandlerFactory.java b/api/kogito-api/src/main/java/org/kie/kogito/internal/process/workitem/KogitoWorkItemHandlerFactory.java index 0b9438bb8c1..307bbb2eedb 100644 --- a/api/kogito-api/src/main/java/org/kie/kogito/internal/process/workitem/KogitoWorkItemHandlerFactory.java +++ b/api/kogito-api/src/main/java/org/kie/kogito/internal/process/workitem/KogitoWorkItemHandlerFactory.java @@ -26,7 +26,7 @@ public interface KogitoWorkItemHandlerFactory { public static List findAllKogitoWorkItemHandlersRegistered() { List handlers = new ArrayList<>(); - ServiceLoader.load(KogitoWorkItemHandlerFactory.class).stream() + ServiceLoader.load(KogitoWorkItemHandlerFactory.class, Thread.currentThread().getContextClassLoader()).stream() .map(ServiceLoader.Provider::get) .map(KogitoWorkItemHandlerFactory::provide) .flatMap(List::stream) diff --git a/api/kogito-api/src/main/java/org/kie/kogito/internal/usertask/event/KogitoUserTaskEventSupport.java b/api/kogito-api/src/main/java/org/kie/kogito/internal/usertask/event/KogitoUserTaskEventSupport.java index 4adc903a217..5ec63f27040 100644 --- a/api/kogito-api/src/main/java/org/kie/kogito/internal/usertask/event/KogitoUserTaskEventSupport.java +++ b/api/kogito-api/src/main/java/org/kie/kogito/internal/usertask/event/KogitoUserTaskEventSupport.java @@ -23,6 +23,7 @@ import org.kie.kogito.usertask.UserTaskEventListener; import org.kie.kogito.usertask.UserTaskInstance; +import org.kie.kogito.usertask.lifecycle.UserTaskState; import org.kie.kogito.usertask.model.Attachment; import org.kie.kogito.usertask.model.Comment; @@ -37,7 +38,7 @@ enum AssignmentType { void fireOneUserTaskStateChange( UserTaskInstance instance, - String oldPhaseStatus, String newPhaseStatus); + UserTaskState oldPhaseStatus, UserTaskState newPhaseStatus); void fireOnUserTaskNotStartedDeadline( UserTaskInstance instance, diff --git a/api/kogito-api/src/main/java/org/kie/kogito/process/workitem/TaskMetaEntity.java b/api/kogito-api/src/main/java/org/kie/kogito/process/workitem/TaskMetaEntity.java index 84f07f496ab..49150a0af6a 100644 --- a/api/kogito-api/src/main/java/org/kie/kogito/process/workitem/TaskMetaEntity.java +++ b/api/kogito-api/src/main/java/org/kie/kogito/process/workitem/TaskMetaEntity.java @@ -57,6 +57,10 @@ public String getUpdatedBy() { return updatedBy; } + public void setId(K id) { + this.id = id; + } + public K getId() { return id; } diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTask.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTask.java index 8910256e68d..11d762195a5 100644 --- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTask.java +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTask.java @@ -109,4 +109,6 @@ public interface UserTask { Collection> getNotCompletedReassigments(); + UserTaskAssignmentStrategy getAssignmentStrategy(); + } diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskAssignmentStrategy.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskAssignmentStrategy.java new file mode 100644 index 00000000000..985a5d25572 --- /dev/null +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskAssignmentStrategy.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.usertask; + +import java.util.Optional; + +import org.kie.kogito.auth.IdentityProvider; + +public interface UserTaskAssignmentStrategy { + + static final String DEFAULT_NAME = "default"; + + default String getName() { + return getClass().getName(); + } + + Optional computeAssigment(UserTaskInstance userTaskInstance, IdentityProvider identityProvider); + +} diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskAssignmentStrategyConfig.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskAssignmentStrategyConfig.java new file mode 100644 index 00000000000..b9bc180a529 --- /dev/null +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskAssignmentStrategyConfig.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.usertask; + +import java.util.List; + +public interface UserTaskAssignmentStrategyConfig { + + List userTaskAssignmentStrategies(); + + UserTaskAssignmentStrategy forName(String name); + + default UserTaskAssignmentStrategy defaultUserTaskAssignmentStrategy() { + return forName(UserTaskAssignmentStrategy.DEFAULT_NAME); + } +} diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskConfig.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskConfig.java index ee031f84188..2bebb31ef62 100644 --- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskConfig.java +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskConfig.java @@ -28,6 +28,8 @@ public interface UserTaskConfig extends KogitoConfig { UserTaskEventListenerConfig userTaskEventListeners(); + UserTaskAssignmentStrategyConfig userTaskAssignmentStrategies(); + UserTaskLifeCycle userTaskLifeCycle(); UnitOfWorkManager unitOfWorkManager(); @@ -36,4 +38,6 @@ public interface UserTaskConfig extends KogitoConfig { IdentityProvider identityProvider(); + UserTaskInstances userTaskInstances(); + } diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstance.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstance.java index 9e73f146240..4a0023a0c41 100644 --- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstance.java +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstance.java @@ -22,8 +22,8 @@ import java.util.Map; import java.util.Set; +import org.kie.kogito.auth.IdentityProvider; import org.kie.kogito.usertask.lifecycle.UserTaskState; -import org.kie.kogito.usertask.lifecycle.UserTaskTransitionToken; import org.kie.kogito.usertask.model.Attachment; import org.kie.kogito.usertask.model.Comment; @@ -35,19 +35,15 @@ public interface UserTaskInstance { UserTaskState getStatus(); + String getUserTaskId(); + boolean hasActualOwner(); - void setActuaOwner(String string); + void setActuaOwner(String actualOwner); String getActualOwner(); - UserTaskTransitionToken createTransitionToken(String transitionId, Map data); - - void transition(UserTaskTransitionToken token); - - void complete(); - - void abort(); + void transition(String transitionId, Map data, IdentityProvider identityProvider); String getExternalReferenceId(); @@ -59,6 +55,14 @@ public interface UserTaskInstance { Map getMetadata(); + Map getOutputs(); + + Map getInputs(); + + void setInput(String key, Object value); + + void setOutput(String key, Object value); + /** * Returns potential users that can work on this task * @@ -94,23 +98,24 @@ public interface UserTaskInstance { */ Set getExcludedUsers(); - void addAttachment(Attachment attachment); + Attachment findAttachmentById(String attachmentId); - void updateAttachment(Attachment newAttachment); + Attachment addAttachment(Attachment attachment); - void removeAttachment(Attachment oldAttachment); + Attachment updateAttachment(Attachment newAttachment); - void addComment(Comment comment); + Attachment removeAttachment(Attachment oldAttachment); - void updateComment(Comment newComment); + Collection getAttachments(); - void removeComment(Comment comment); + Comment findCommentById(String commentId); - Collection getComments(); + Comment addComment(Comment comment); - Collection getAttachments(); + Comment updateComment(Comment newComment); - Attachment findAttachmentById(String attachmentId); + Comment removeComment(Comment comment); + + Collection getComments(); - Comment findCommentById(String commentId); } diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstances.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstances.java index 57e956bd87f..7f294b15fed 100644 --- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstances.java +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstances.java @@ -18,11 +18,20 @@ */ package org.kie.kogito.usertask; +import java.util.List; import java.util.Optional; import java.util.function.Function; +import org.kie.kogito.auth.IdentityProvider; + public interface UserTaskInstances { + void setReconnectUserTaskInstance(Function reconnectUserTaskInstance); + + void setDisconnectUserTaskInstance(Function disconnectUserTaskInstance); + + List findByIdentity(IdentityProvider identityProvider); + Optional findById(String userTaskInstanceId); boolean exists(String userTaskInstanceId); @@ -33,8 +42,4 @@ public interface UserTaskInstances { UserTaskInstance remove(String userTaskInstanceId); - void setReconnectUserTaskInstance(Function reconnectUserTaskInstance); - - void setDisconnectUserTaskInstance(Function disconnectUserTaskInstance); - } diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskService.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskService.java new file mode 100644 index 00000000000..70cfdfbdc25 --- /dev/null +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskService.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.usertask; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.kie.kogito.auth.IdentityProvider; +import org.kie.kogito.usertask.model.Attachment; +import org.kie.kogito.usertask.model.Comment; +import org.kie.kogito.usertask.view.UserTaskTransitionView; +import org.kie.kogito.usertask.view.UserTaskView; + +public interface UserTaskService { + + Optional getUserTaskInstance(String taskId, IdentityProvider identity); + + List list(IdentityProvider identity); + + Optional transition(String taskId, String transitionId, Map data, IdentityProvider identity); + + List allowedTransitions(String taskId, IdentityProvider identity); + + Optional setOutputs(String taskId, Map data, IdentityProvider identity); + + Optional setInputs(String taskId, Map data, IdentityProvider identity); + + List getComments(String taskId, IdentityProvider identity); + + Optional getComment(String taskId, String commentId, IdentityProvider identity); + + Optional addComment(String taskId, Comment comment, IdentityProvider identity); + + Optional updateComment(String taskId, Comment comment, IdentityProvider identity); + + Optional removeComment(String taskId, String commentId, IdentityProvider identity); + + List getAttachments(String taskId, IdentityProvider identity); + + Optional getAttachment(String taskId, String commentId, IdentityProvider identity); + + Optional addAttachment(String taskId, Attachment comment, IdentityProvider identity); + + Optional updateAttachment(String taskId, Attachment comment, IdentityProvider identity); + + Optional removeAttachment(String taskId, String commentId, IdentityProvider identity); +} diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTasks.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTasks.java index 3d31047cad2..0253644c0f2 100644 --- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTasks.java +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTasks.java @@ -24,6 +24,8 @@ public interface UserTasks extends KogitoEngine { + UserTaskInstances instances(); + UserTask userTaskById(String userTaskId); Collection userTaskIds(); diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/events/UserTaskStateEvent.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/events/UserTaskStateEvent.java index 196b76aafbb..e5471cc96f3 100644 --- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/events/UserTaskStateEvent.java +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/events/UserTaskStateEvent.java @@ -19,10 +19,12 @@ package org.kie.kogito.usertask.events; +import org.kie.kogito.usertask.lifecycle.UserTaskState; + public interface UserTaskStateEvent extends UserTaskEvent { - String getNewStatus(); + UserTaskState getNewStatus(); - String getOldStatus(); + UserTaskState getOldStatus(); } diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskLifeCycle.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskLifeCycle.java index eb8290884a9..09170f4d1cc 100644 --- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskLifeCycle.java +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskLifeCycle.java @@ -19,14 +19,16 @@ package org.kie.kogito.usertask.lifecycle; +import java.util.List; import java.util.Map; import java.util.Optional; +import org.kie.kogito.auth.IdentityProvider; import org.kie.kogito.usertask.UserTaskInstance; public interface UserTaskLifeCycle { - Optional transition(UserTaskInstance userTaskInstance, UserTaskTransitionToken transition); + Optional transition(UserTaskInstance userTaskInstance, UserTaskTransitionToken transition, IdentityProvider identity); UserTaskTransitionToken newTransitionToken(String transitionId, UserTaskInstance userTaskInstance, Map data); @@ -34,4 +36,6 @@ public interface UserTaskLifeCycle { UserTaskTransitionToken newAbortTransitionToken(UserTaskInstance userTaskInstance, Map emptyMap); + List allowedTransitions(UserTaskInstance ut); + } diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskState.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskState.java index 71ba89b94d0..63a3242ede4 100644 --- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskState.java +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskState.java @@ -19,7 +19,6 @@ package org.kie.kogito.usertask.lifecycle; import java.util.Objects; -import java.util.Optional; public class UserTaskState { @@ -69,8 +68,8 @@ public String getName() { return name; } - public Optional isTerminate() { - return Optional.ofNullable(terminate); + public boolean isTerminate() { + return terminate != null; } @Override @@ -91,7 +90,7 @@ public boolean equals(Object obj) { } public static UserTaskState initalized() { - return of(null); + return of("Created"); } @Override diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionException.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionException.java new file mode 100644 index 00000000000..3e98d19bdc4 --- /dev/null +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionException.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.usertask.lifecycle; + +public class UserTaskTransitionException extends RuntimeException { + + private static final long serialVersionUID = -9127576077607542859L; + + public UserTaskTransitionException(String msg) { + super(msg); + } + +} diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionExecutor.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionExecutor.java index 6124901d28f..79eb36a5a0f 100644 --- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionExecutor.java +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionExecutor.java @@ -20,10 +20,11 @@ import java.util.Optional; +import org.kie.kogito.auth.IdentityProvider; import org.kie.kogito.usertask.UserTaskInstance; public interface UserTaskTransitionExecutor { - Optional execute(UserTaskInstance transition, UserTaskTransitionToken token); + Optional execute(UserTaskInstance transition, UserTaskTransitionToken token, IdentityProvider identity); } \ No newline at end of file diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionToken.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionToken.java index db1dd9e8d72..8277f4d6d56 100644 --- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionToken.java +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/lifecycle/UserTaskTransitionToken.java @@ -22,7 +22,11 @@ public interface UserTaskTransitionToken { - UserTaskTransition transition(); + String transitionId(); + + UserTaskState source(); + + UserTaskState target(); Map data(); } diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Attachment.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Attachment.java index 04ff75b130a..d032eca9eaf 100644 --- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Attachment.java +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Attachment.java @@ -27,6 +27,10 @@ public class Attachment extends TaskMetaEntity { private static final long serialVersionUID = 1L; private String name; + public Attachment() { + + } + public Attachment(String id, String user) { super(id, user); } diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Comment.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Comment.java index 74a801b7112..fe2a3fdf457 100644 --- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Comment.java +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/Comment.java @@ -24,6 +24,9 @@ public class Comment extends TaskMetaEntity { private static final long serialVersionUID = -9106249675352498780L; + public Comment() { + } + public Comment(String id, String user) { super(id, user); } diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/CommentInfo.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/CommentInfo.java new file mode 100644 index 00000000000..5cbfd1404ba --- /dev/null +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/CommentInfo.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.usertask.model; + +public class CommentInfo { + + private String comment; + + public CommentInfo() { + + } + + public CommentInfo(String comment) { + this.comment = comment; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } +} diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/view/UserTaskTransitionView.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/view/UserTaskTransitionView.java new file mode 100644 index 00000000000..3f22f091e7c --- /dev/null +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/view/UserTaskTransitionView.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.usertask.view; + +import org.kie.kogito.usertask.lifecycle.UserTaskState; + +public class UserTaskTransitionView { + + private String transitionId; + private UserTaskState source; + private UserTaskState target; + + public String getTransitionId() { + return transitionId; + } + + public void setTransitionId(String transitionId) { + this.transitionId = transitionId; + } + + public UserTaskState getSource() { + return source; + } + + public void setSource(UserTaskState source) { + this.source = source; + } + + public UserTaskState getTarget() { + return target; + } + + public void setTarget(UserTaskState target) { + this.target = target; + } + +} diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/view/UserTaskView.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/view/UserTaskView.java new file mode 100644 index 00000000000..660ea4c8446 --- /dev/null +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/view/UserTaskView.java @@ -0,0 +1,178 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.usertask.view; + +import java.util.Map; +import java.util.Set; + +import org.kie.kogito.usertask.lifecycle.UserTaskState; + +public class UserTaskView { + + private String id; + + private String userTaskId; + + private UserTaskState status; + + private String taskName; + private String taskDescription; + private Integer taskPriority; + private Set potentialUsers; + private Set potentialGroups; + private Set adminUsers; + private Set adminGroups; + private Set excludedUsers; + private String externalReferenceId; + private String actualOwner; + + private Map inputs; + private Map outputs; + + private Map metadata; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getUserTaskId() { + return userTaskId; + } + + public void setUserTaskId(String userTaskId) { + this.userTaskId = userTaskId; + } + + public UserTaskState getStatus() { + return status; + } + + public void setStatus(UserTaskState status) { + this.status = status; + } + + public String getTaskName() { + return taskName; + } + + public void setTaskName(String taskName) { + this.taskName = taskName; + } + + public String getTaskDescription() { + return taskDescription; + } + + public void setTaskDescription(String taskDescription) { + this.taskDescription = taskDescription; + } + + public Integer getTaskPriority() { + return taskPriority; + } + + public void setTaskPriority(Integer taskPriority) { + this.taskPriority = taskPriority; + } + + public Set getPotentialUsers() { + return potentialUsers; + } + + public void setPotentialUsers(Set potentialUsers) { + this.potentialUsers = potentialUsers; + } + + public Set getPotentialGroups() { + return potentialGroups; + } + + public void setPotentialGroups(Set potentialGroups) { + this.potentialGroups = potentialGroups; + } + + public Set getAdminUsers() { + return adminUsers; + } + + public void setAdminUsers(Set adminUsers) { + this.adminUsers = adminUsers; + } + + public Set getAdminGroups() { + return adminGroups; + } + + public void setAdminGroups(Set adminGroups) { + this.adminGroups = adminGroups; + } + + public Set getExcludedUsers() { + return excludedUsers; + } + + public void setExcludedUsers(Set excludedUsers) { + this.excludedUsers = excludedUsers; + } + + public String getExternalReferenceId() { + return externalReferenceId; + } + + public void setExternalReferenceId(String externalReferenceId) { + this.externalReferenceId = externalReferenceId; + } + + public String getActualOwner() { + return actualOwner; + } + + public void setActualOwner(String actualOwner) { + this.actualOwner = actualOwner; + } + + public Map getInputs() { + return inputs; + } + + public void setInputs(Map inputs) { + this.inputs = inputs; + } + + public Map getOutputs() { + return outputs; + } + + public void setOutputs(Map outputs) { + this.outputs = outputs; + } + + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + +} diff --git a/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/DefaultInstanceEventBatch.java b/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/DefaultInstanceEventBatch.java index c4f6cd76b2f..df94b1183ae 100644 --- a/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/DefaultInstanceEventBatch.java +++ b/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/DefaultInstanceEventBatch.java @@ -68,7 +68,7 @@ public int compare(DataEvent event1, DataEvent event2) { @Override public void append(Object event) { - LOG.info("event generated {}", event); + LOG.trace("event generated {}", event); this.dataEventAdapters.stream().filter(a -> a.accept(event)).map(a -> a.adapt(event)).forEach(this.processedEvents::add); } diff --git a/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/UserTaskStateEventDataEventAdapter.java b/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/UserTaskStateEventDataEventAdapter.java index fe577ac0c29..af70b37ae35 100644 --- a/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/UserTaskStateEventDataEventAdapter.java +++ b/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/UserTaskStateEventDataEventAdapter.java @@ -54,9 +54,9 @@ public DataEvent adapt(Object payload) { .userTaskDescription(event.getUserTaskInstance().getTaskDescription()) .userTaskPriority(priorityStr) .userTaskReferenceName(event.getUserTask().getReferenceName()) - .state(event.getNewStatus()) + .state(event.getNewStatus().getName()) .actualOwner(event.getUserTaskInstance().getActualOwner()) - .eventType(isTransition(event) ? event.getNewStatus() : "Modify") + .eventType(isTransition(event) ? event.getNewStatus().getName() : "Modify") .processInstanceId((String) event.getUserTaskInstance().getMetadata().get("ProcessInstanceId")); UserTaskInstanceStateEventBody body = builder.build(); diff --git a/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/AbstractNodeVisitor.java b/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/AbstractNodeVisitor.java index a8ce5a76b8d..1946318d05c 100644 --- a/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/AbstractNodeVisitor.java +++ b/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/AbstractNodeVisitor.java @@ -53,7 +53,6 @@ import org.jbpm.workflow.core.impl.ExtendedNodeImpl; import org.jbpm.workflow.core.impl.NodeImpl; import org.jbpm.workflow.core.node.Assignment; -import org.jbpm.workflow.core.node.HumanTaskNode; import org.jbpm.workflow.core.node.StartNode; import org.jbpm.workflow.core.node.Transformation; import org.kie.api.definition.process.Connection; @@ -117,7 +116,7 @@ public ReturnValueEvaluatorBuilderService getReturnValueEvaluatorBuilderService( public void visitNode(T node, BlockStmt body, VariableScope variableScope, ProcessMetaData metadata) { visitNode(FACTORY_FIELD_NAME, node, body, variableScope, metadata); - if (isAdHocNode(node) && !(node instanceof HumanTaskNode)) { + if (isAdHocNode(node)) { metadata.addSignal(node.getName(), null); } if (isExtendedNode(node)) { diff --git a/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ProcessToExecModelGenerator.java b/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ProcessToExecModelGenerator.java index 253d24a7f0a..678c4657330 100644 --- a/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ProcessToExecModelGenerator.java +++ b/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/ProcessToExecModelGenerator.java @@ -29,7 +29,7 @@ import org.jbpm.process.core.context.variable.Variable; import org.jbpm.process.core.context.variable.VariableScope; import org.jbpm.workflow.core.impl.WorkflowProcessImpl; -import org.jbpm.workflow.core.node.HumanTaskNode; +import org.jbpm.workflow.core.node.WorkItemNode; import org.kie.api.definition.process.Node; import org.kie.api.definition.process.WorkflowProcess; import org.kie.kogito.ProcessInput; @@ -243,27 +243,26 @@ public static String extractModelClassName(String processId) { return sanitizeClassName(extractProcessId(processId) + MODEL_CLASS_SUFFIX); } - public List generateUserTaskModel(WorkflowProcess process) { + public List generateWorkItemModel(WorkflowProcess process) { String packageName = process.getPackageName(); - List userTaskModels = new ArrayList<>(); + List workItemTaskModels = new ArrayList<>(); VariableScope variableScope = (VariableScope) ((org.jbpm.process.core.Process) process).getDefaultContext( VariableScope.VARIABLE_SCOPE); for (Node node : ((WorkflowProcessImpl) process).getNodesRecursively()) { - if (node instanceof HumanTaskNode) { - HumanTaskNode humanTaskNode = (HumanTaskNode) node; - VariableScope nodeVariableScope = (VariableScope) ((ContextContainer) humanTaskNode + if (node instanceof WorkItemNode workItemNode) { + VariableScope nodeVariableScope = (VariableScope) ((ContextContainer) workItemNode .getParentContainer()).getDefaultContext(VariableScope.VARIABLE_SCOPE); if (nodeVariableScope == null) { nodeVariableScope = variableScope; } - userTaskModels.add(new UserTaskModelMetaData(packageName, variableScope, nodeVariableScope, - humanTaskNode, process.getId())); + workItemTaskModels.add(new WorkItemModelMetaData(packageName, variableScope, nodeVariableScope, + workItemNode, process.getId())); } } - return userTaskModels; + return workItemTaskModels; } public static String extractProcessId(String processId) { diff --git a/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/UserTaskModelMetaData.java b/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/WorkItemModelMetaData.java similarity index 90% rename from jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/UserTaskModelMetaData.java rename to jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/WorkItemModelMetaData.java index 3d648ad2e2f..5ae74187907 100644 --- a/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/UserTaskModelMetaData.java +++ b/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/WorkItemModelMetaData.java @@ -30,7 +30,7 @@ import org.jbpm.process.core.datatype.DataType; import org.jbpm.process.core.datatype.DataTypeResolver; import org.jbpm.util.PatternConstants; -import org.jbpm.workflow.core.node.HumanTaskNode; +import org.jbpm.workflow.core.node.WorkItemNode; import org.kie.api.definition.process.WorkflowElementIdentifier; import org.kie.kogito.UserTask; import org.kie.kogito.UserTaskParam; @@ -70,7 +70,7 @@ import static org.jbpm.ruleflow.core.Metadata.CUSTOM_AUTO_START; import static org.kie.kogito.internal.utils.ConversionUtils.sanitizeClassName; -public class UserTaskModelMetaData { +public class WorkItemModelMetaData { private static final String TASK_INTPUT_CLASS_SUFFIX = "TaskInput"; private static final String TASK_OUTTPUT_CLASS_SUFFIX = "TaskOutput"; @@ -86,7 +86,7 @@ public class UserTaskModelMetaData { private final VariableScope processVariableScope; private final VariableScope variableScope; - private final HumanTaskNode humanTaskNode; + private final WorkItemNode workItemNode; private final String processId; private String inputModelClassName; @@ -98,20 +98,20 @@ public class UserTaskModelMetaData { private String taskModelClassName; private String taskModelClassSimpleName; - public UserTaskModelMetaData(String packageName, VariableScope processVariableScope, VariableScope variableScope, HumanTaskNode humanTaskNode, String processId) { + public WorkItemModelMetaData(String packageName, VariableScope processVariableScope, VariableScope variableScope, WorkItemNode workItemNode, String processId) { this.packageName = packageName; this.processVariableScope = processVariableScope; this.variableScope = variableScope; - this.humanTaskNode = humanTaskNode; + this.workItemNode = workItemNode; this.processId = processId; - this.inputModelClassSimpleName = sanitizeClassName(ProcessToExecModelGenerator.extractProcessId(processId) + "_" + humanTaskNode.getId().toExternalFormat() + "_" + TASK_INTPUT_CLASS_SUFFIX); + this.inputModelClassSimpleName = sanitizeClassName(ProcessToExecModelGenerator.extractProcessId(processId) + "_" + workItemNode.getId().toExternalFormat() + "_" + TASK_INTPUT_CLASS_SUFFIX); this.inputModelClassName = packageName + '.' + inputModelClassSimpleName; - this.outputModelClassSimpleName = sanitizeClassName(ProcessToExecModelGenerator.extractProcessId(processId) + "_" + humanTaskNode.getId().toExternalFormat() + "_" + TASK_OUTTPUT_CLASS_SUFFIX); + this.outputModelClassSimpleName = sanitizeClassName(ProcessToExecModelGenerator.extractProcessId(processId) + "_" + workItemNode.getId().toExternalFormat() + "_" + TASK_OUTTPUT_CLASS_SUFFIX); this.outputModelClassName = packageName + '.' + outputModelClassSimpleName; - this.taskModelClassSimpleName = sanitizeClassName(ProcessToExecModelGenerator.extractProcessId(processId) + "_" + humanTaskNode.getId().toExternalFormat() + "_" + TASK_MODEL_CLASS_SUFFIX); + this.taskModelClassSimpleName = sanitizeClassName(ProcessToExecModelGenerator.extractProcessId(processId) + "_" + workItemNode.getId().toExternalFormat() + "_" + TASK_MODEL_CLASS_SUFFIX); this.taskModelClassName = packageName + '.' + taskModelClassSimpleName; } @@ -144,21 +144,21 @@ public String getTaskModelClassName() { } public String getName() { - return (String) humanTaskNode.getWork().getParameters().getOrDefault(TASK_NAME, humanTaskNode.getName()); + return (String) workItemNode.getWork().getParameters().getOrDefault(TASK_NAME, workItemNode.getName()); } public String getNodeName() { - return humanTaskNode.getName(); + return workItemNode.getName(); } public WorkflowElementIdentifier getId() { - return humanTaskNode.getId(); + return workItemNode.getId(); } private void addUserTaskAnnotation(ClassOrInterfaceDeclaration modelClass) { - String taskName = (String) humanTaskNode.getWork().getParameter(TASK_NAME); + String taskName = (String) workItemNode.getWork().getParameter(TASK_NAME); if (taskName == null) - taskName = humanTaskNode.getName(); + taskName = workItemNode.getName(); modelClass.addAndGetAnnotation(UserTask.class).addPair("taskName", new StringLiteralExpr(taskName)).addPair("processName", new StringLiteralExpr(processId)); } @@ -190,8 +190,8 @@ private CompilationUnit compilationUnitInput() { NameExpr item = new NameExpr("item"); // map is task input -> context variable / process variable - Map inputTypes = humanTaskNode.getIoSpecification().getInputTypes(); - for (Entry entry : humanTaskNode.getIoSpecification().getInputMapping().entrySet()) { + Map inputTypes = workItemNode.getIoSpecification().getInputTypes(); + for (Entry entry : workItemNode.getIoSpecification().getInputMapping().entrySet()) { if (INTERNAL_FIELDS.contains(entry.getKey())) { continue; } @@ -235,7 +235,7 @@ private CompilationUnit compilationUnitInput() { AssignExpr.Operator.ASSIGN)); } - for (Entry entry : humanTaskNode.getWork().getParameters().entrySet()) { + for (Entry entry : workItemNode.getWork().getParameters().entrySet()) { if (entry.getValue() == null || INTERNAL_FIELDS.contains(entry.getKey())) { continue; @@ -275,7 +275,6 @@ private CompilationUnit compilationUnitInput() { return compilationUnit; } - @SuppressWarnings({ "unchecked" }) private CompilationUnit compilationUnitOutput() { CompilationUnit compilationUnit = parse(this.getClass().getResourceAsStream("/class-templates/TaskOutputTemplate.java")); compilationUnit.setPackageDeclaration(packageName); @@ -303,8 +302,8 @@ private CompilationUnit compilationUnitOutput() { HashMap.class.getSimpleName() + "<>"), NodeList.nodeList()), AssignExpr.Operator.ASSIGN)); // map is task output -> context variable / process variable - Map outputTypes = humanTaskNode.getIoSpecification().getOutputTypes(); - for (Entry entry : humanTaskNode.getIoSpecification().getOutputMappingBySources().entrySet()) { + Map outputTypes = workItemNode.getIoSpecification().getOutputTypes(); + for (Entry entry : workItemNode.getIoSpecification().getOutputMappingBySources().entrySet()) { if (entry.getValue() == null || INTERNAL_FIELDS.contains(entry.getKey())) { continue; } @@ -382,7 +381,7 @@ private CompilationUnit compilationUnitModel() { } private void addComment(CompilationUnit unit, String prefix) { - unit.addOrphanComment(new LineComment(prefix + " for user task '" + humanTaskNode.getName() + "' in process '" + processId + "'")); + unit.addOrphanComment(new LineComment(prefix + " for user task '" + workItemNode.getName() + "' in process '" + processId + "'")); } private void templateReplacement(NameExpr name) { @@ -407,13 +406,13 @@ public String templateReplacement(String template) { } public boolean isAdHoc() { - return !Boolean.parseBoolean((String) humanTaskNode.getMetaData(CUSTOM_AUTO_START)) - && (humanTaskNode.getIncomingConnections() == null || humanTaskNode.getIncomingConnections().isEmpty()); + return !Boolean.parseBoolean((String) workItemNode.getMetaData(CUSTOM_AUTO_START)) + && (workItemNode.getIncomingConnections() == null || workItemNode.getIncomingConnections().isEmpty()); } public SwitchEntry getModelSwitchEntry() { SwitchEntry entry = new SwitchEntry(); - entry.setLabels(NodeList.nodeList(new StringLiteralExpr(humanTaskNode.getId().toExternalFormat()))); + entry.setLabels(NodeList.nodeList(new StringLiteralExpr(workItemNode.getId().toExternalFormat()))); entry.addStatement(new ReturnStmt(new MethodCallExpr(new NameExpr( taskModelClassSimpleName), new SimpleName("from")).addArgument(WORK_ITEM))); return entry; diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/LightWorkItemManager.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/LightWorkItemManager.java index 16a1519fe48..01b24104652 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/LightWorkItemManager.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/process/instance/LightWorkItemManager.java @@ -74,8 +74,12 @@ public void internalAddWorkItem(InternalKogitoWorkItem workItem) { } @Override - public InternalKogitoWorkItem getWorkItem(String id) { - return workItems.get(id); + public InternalKogitoWorkItem getWorkItem(String workItemId) { + InternalKogitoWorkItem workItem = workItems.get(workItemId); + if (workItem == null) { + throw new WorkItemNotFoundException("Work Item (" + workItemId + ") does not exist", workItemId); + } + return workItem; } @Override @@ -131,7 +135,6 @@ public void retryWorkItem(String workItemId, Map params) { KogitoWorkItemHandler handler = getWorkItemHandler(workItem); WorkItemTransition transition = handler.startingTransition(Collections.emptyMap()); transitionWorkItem(workItem, transition, true); - } @Override @@ -143,7 +146,6 @@ public void completeWorkItem(String workItemId, Map data, Policy KogitoProcessInstance processInstance = processInstanceManager.getProcessInstance(workItem.getProcessInstanceStringId()); workItem.setState(KogitoWorkItem.COMPLETED); processInstance.signalEvent("workItemCompleted", workItem); - } @Override @@ -160,10 +162,7 @@ public void abortWorkItem(String workItemId, Policy... policies) { @Override public T updateWorkItem(String workItemId, Function updater, Policy... policies) { - InternalKogitoWorkItem workItem = workItems.get(workItemId); - if (workItem == null) { - throw new WorkItemNotFoundException(workItemId); - } + InternalKogitoWorkItem workItem = getWorkItem(workItemId); Stream.of(policies).forEach(p -> p.enforce(workItem)); T results = updater.apply(workItem); return results; @@ -178,10 +177,7 @@ public void internalCompleteWorkItem(InternalKogitoWorkItem workItem) { @Override public void transitionWorkItem(String workItemId, WorkItemTransition transition) { - InternalKogitoWorkItem workItem = workItems.get(workItemId); - if (workItem == null) { - throw new WorkItemNotFoundException("Work Item (" + workItemId + ") does not exist", workItemId); - } + InternalKogitoWorkItem workItem = getWorkItem(workItemId); transitionWorkItem(workItem, transition, true); } @@ -209,7 +205,7 @@ private void transitionWorkItem(InternalKogitoWorkItem workItem, WorkItemTransit } if (lastTransition.termination().isPresent()) { - internalRemoveWorkItem(workItem.getStringId()); + if (signal) { switch (lastTransition.termination().get()) { case COMPLETE: @@ -222,6 +218,7 @@ private void transitionWorkItem(InternalKogitoWorkItem workItem, WorkItemTransit break; } } + } } diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java index 7ae6edf3fb1..93554930fa8 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/WorkflowProcessInstanceImpl.java @@ -76,6 +76,7 @@ import org.jbpm.workflow.instance.node.EventSubProcessNodeInstance; import org.jbpm.workflow.instance.node.FaultNodeInstance; import org.jbpm.workflow.instance.node.StateBasedNodeInstance; +import org.jbpm.workflow.instance.node.WorkItemNodeInstance; import org.kie.api.definition.process.NodeContainer; import org.kie.api.definition.process.WorkflowElementIdentifier; import org.kie.api.runtime.rule.AgendaFilter; @@ -464,6 +465,11 @@ public void setState(final int state) { @Override public void disconnect() { + for (org.kie.api.runtime.process.NodeInstance nodeInstance : getNodeInstances(true)) { + if (nodeInstance instanceof WorkItemNodeInstance workItemNodeInstance) { + workItemNodeInstance.internalRemoveWorkItem(); + } + } removeEventListeners(); unregisterExternalEventNodeListeners(); @@ -478,11 +484,17 @@ public void disconnect() { @Override public void reconnect() { super.reconnect(); + for (org.kie.api.runtime.process.NodeInstance nodeInstance : getNodeInstances(true)) { + if (nodeInstance instanceof WorkItemNodeInstance workItemNodeInstance) { + workItemNodeInstance.internalRegisterWorkItem(); + } + } for (NodeInstance nodeInstance : nodeInstances) { if (nodeInstance instanceof EventBasedNodeInstanceInterface) { ((EventBasedNodeInstanceInterface) nodeInstance).addEventListeners(); } } + registerExternalEventNodeListeners(); } diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/WorkItemNodeInstance.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/WorkItemNodeInstance.java index 5af0a5de03c..be1ff4ddfab 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/WorkItemNodeInstance.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/WorkItemNodeInstance.java @@ -274,7 +274,7 @@ public void triggerCompleted(InternalKogitoWorkItem workItem) { if (workItemNode != null && workItem.getState() == COMPLETED) { validateWorkItemResultVariable(getProcessInstance().getProcessName(), workItemNode.getOutAssociations(), workItem); Map outputs = new HashMap<>(workItem.getResults()); - if (workItem.getActualOwner() != null) { + if (workItem.getActualOwner() != null && !outputs.containsKey("ActorId")) { outputs.put("ActorId", workItem.getActualOwner()); } NodeIoHelper.processOutputs(this, varRef -> outputs.get(varRef), varName -> this.getVariable(varName)); @@ -284,6 +284,8 @@ public void triggerCompleted(InternalKogitoWorkItem workItem) { setMetaData("NodeType", workItem.getName()); mapDynamicOutputData(workItem.getResults()); } + + internalRemoveWorkItem(); if (isInversionOfControl()) { KieRuntime kruntime = getProcessInstance().getKnowledgeRuntime(); kruntime.update(kruntime.getFactHandle(this), this); diff --git a/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessInstance.java b/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessInstance.java index 39d29374656..99f32900da2 100644 --- a/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessInstance.java +++ b/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/AbstractProcessInstance.java @@ -73,9 +73,13 @@ import org.kie.kogito.process.flexible.Milestone; import org.kie.kogito.process.workitems.InternalKogitoWorkItem; import org.kie.kogito.services.uow.ProcessInstanceWorkUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public abstract class AbstractProcessInstance implements ProcessInstance { + private static final Logger LOG = LoggerFactory.getLogger(AbstractProcessInstance.class); + private static final String KOGITO_PROCESS_INSTANCE = "KogitoProcessInstance"; protected final T variables; @@ -156,6 +160,7 @@ public AbstractProcessInstance(AbstractProcess process, T variables, ProcessR } protected void reconnect() { + LOG.debug("reconnect process {}", processInstance.getId()); //set correlation if (correlationInstance.isEmpty()) { correlationInstance = process().correlations().findByCorrelatedId(id()); @@ -169,12 +174,6 @@ protected void reconnect() { processInstance.setMetaData(KOGITO_PROCESS_INSTANCE, this); addCompletionEventListener(); - for (org.kie.api.runtime.process.NodeInstance nodeInstance : processInstance.getNodeInstances()) { - if (nodeInstance instanceof WorkItemNodeInstance) { - ((WorkItemNodeInstance) nodeInstance).internalRegisterWorkItem(); - } - } - unbind(variables, processInstance.getVariables()); } @@ -193,15 +192,12 @@ private void removeCompletionListener() { } protected void disconnect() { + if (processInstance == null) { return; } - for (org.kie.api.runtime.process.NodeInstance nodeInstance : processInstance.getNodeInstances()) { - if (nodeInstance instanceof WorkItemNodeInstance) { - ((WorkItemNodeInstance) nodeInstance).internalRemoveWorkItem(); - } - } + LOG.debug("disconnect process {}", processInstance.getId()); processInstance.disconnect(); processInstance.setMetaData(KOGITO_PROCESS_INSTANCE, null); @@ -248,6 +244,7 @@ public void internalRemoveProcessInstance(Consumer> r if (processInstance.getKnowledgeRuntime() != null) { disconnect(); } + processInstance = null; } @@ -337,7 +334,7 @@ public T variables() { @Override public int status() { - return this.status; + return status; } @Override @@ -556,12 +553,14 @@ private boolean enforce(KogitoWorkItem kogitoWorkItem, Policy... policies) { @Override public void completeWorkItem(String id, Map variables, Policy... policies) { + syncWorkItems(); getProcessRuntime().getKogitoProcessRuntime().getKogitoWorkItemManager().completeWorkItem(id, variables, policies); removeOnFinish(); } @Override public R updateWorkItem(String id, Function updater, Policy... policies) { + syncWorkItems(); R result = getProcessRuntime().getKogitoProcessRuntime().getKogitoWorkItemManager().updateWorkItem(id, updater, policies); addToUnitOfWork(pi -> ((MutableProcessInstances) process.instances()).update(pi.id(), pi)); return result; @@ -569,16 +568,26 @@ public R updateWorkItem(String id, Function updater, Poli @Override public void abortWorkItem(String id, Policy... policies) { + syncWorkItems(); getProcessRuntime().getKogitoProcessRuntime().getKogitoWorkItemManager().abortWorkItem(id, policies); removeOnFinish(); } @Override public void transitionWorkItem(String id, WorkItemTransition transition) { + syncWorkItems(); getProcessRuntime().getKogitoProcessRuntime().getKogitoWorkItemManager().transitionWorkItem(id, transition); removeOnFinish(); } + private void syncWorkItems() { + for (org.kie.api.runtime.process.NodeInstance nodeInstance : processInstance().getNodeInstances(true)) { + if (nodeInstance instanceof WorkItemNodeInstance workItemNodeInstance) { + workItemNodeInstance.internalRegisterWorkItem(); + } + } + } + @Override public Set> events() { return processInstance().getEventDescriptions(); diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandler.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandler.java index 4fb06024d3e..d612a692961 100644 --- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandler.java +++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandler.java @@ -18,31 +18,27 @@ */ package org.kie.kogito.jbpm.usertask.handler; -import java.util.Map; +import java.util.Collections; import java.util.Optional; import java.util.Set; -import org.kie.kogito.auth.SecurityPolicy; -import org.kie.kogito.internal.process.workitem.InvalidTransitionException; +import org.kie.kogito.Application; +import org.kie.kogito.auth.IdentityProviders; import org.kie.kogito.internal.process.workitem.KogitoWorkItem; import org.kie.kogito.internal.process.workitem.KogitoWorkItemHandler; import org.kie.kogito.internal.process.workitem.KogitoWorkItemManager; -import org.kie.kogito.internal.process.workitem.Policy; -import org.kie.kogito.internal.process.workitem.WorkItemLifeCycle; -import org.kie.kogito.internal.process.workitem.WorkItemLifeCyclePhase; -import org.kie.kogito.internal.process.workitem.WorkItemPhaseState; -import org.kie.kogito.internal.process.workitem.WorkItemTerminationType; import org.kie.kogito.internal.process.workitem.WorkItemTransition; import org.kie.kogito.process.workitems.InternalKogitoWorkItem; import org.kie.kogito.process.workitems.impl.DefaultKogitoWorkItemHandler; -import org.kie.kogito.process.workitems.impl.DefaultWorkItemLifeCycle; -import org.kie.kogito.process.workitems.impl.DefaultWorkItemLifeCyclePhase; import org.kie.kogito.usertask.UserTask; import org.kie.kogito.usertask.UserTasks; import org.kie.kogito.usertask.impl.DefaultUserTaskInstance; +import org.kie.kogito.usertask.impl.lifecycle.DefaultUserTaskLifeCycle; import static java.util.Collections.emptyMap; import static java.util.Optional.ofNullable; +import static org.kie.kogito.usertask.impl.lifecycle.DefaultUserTaskLifeCycle.PARAMETER_NOTIFY; +import static org.kie.kogito.usertask.impl.lifecycle.DefaultUserTaskLifeCycle.WORKFLOW_ENGINE_USER; /** * Default Work Item handler based on the standard life cycle @@ -51,29 +47,6 @@ public class UserTaskKogitoWorkItemHandler extends DefaultKogitoWorkItemHandler private static String UT_SEPARATOR = System.getProperty("org.jbpm.ht.user.separator", ","); - public static final WorkItemPhaseState INACTIVE = WorkItemPhaseState.initialized(); - public static final WorkItemPhaseState COMPLETED = WorkItemPhaseState.of("Completed", WorkItemTerminationType.COMPLETE); - public static final WorkItemPhaseState ABORTED = WorkItemPhaseState.of("Aborted", WorkItemTerminationType.ABORT); - public static final WorkItemPhaseState ACTIVATED = WorkItemPhaseState.of("Activated"); - public static final WorkItemPhaseState RESERVED = WorkItemPhaseState.of("Reserved"); - - public static final WorkItemLifeCyclePhase TRANSITION_RESERVED_COMPLETE = - new DefaultWorkItemLifeCyclePhase("complete", RESERVED, COMPLETED, UserTaskKogitoWorkItemHandler::userTaskCompleteWorkItemHandler); - public static final WorkItemLifeCyclePhase TRANSITION_ACTIVATED_COMPLETE = - new DefaultWorkItemLifeCyclePhase("complete", ACTIVATED, COMPLETED, UserTaskKogitoWorkItemHandler::userTaskCompleteFromActiveWorkItemHandler); - public static final WorkItemLifeCyclePhase TRANSITION_RESERVED_ABORT = - new DefaultWorkItemLifeCyclePhase("abort", RESERVED, ABORTED, UserTaskKogitoWorkItemHandler::userTaskAbortWorkItemHandler); - public static final WorkItemLifeCyclePhase TRANSITION_ACTIVATED_ABORT = - new DefaultWorkItemLifeCyclePhase("abort", ACTIVATED, ABORTED, UserTaskKogitoWorkItemHandler::userTaskAbortWorkItemHandler); - public static final WorkItemLifeCyclePhase TRANSITION_ACTIVATED_CLAIM = - new DefaultWorkItemLifeCyclePhase("claim", ACTIVATED, RESERVED, UserTaskKogitoWorkItemHandler::userTaskClaimWorkItemHandler); - public static final WorkItemLifeCyclePhase TRANSITION_CREATED_ACTIVE = - new DefaultWorkItemLifeCyclePhase("activate", INACTIVE, ACTIVATED, UserTaskKogitoWorkItemHandler::userTaskActivateWorkItemHandler); - public static final WorkItemLifeCyclePhase TRANSITION_RESERVED_RELEASE = - new DefaultWorkItemLifeCyclePhase("release", RESERVED, ACTIVATED, UserTaskKogitoWorkItemHandler::userTaskReleaseWorkItemHandler); - public static final WorkItemLifeCyclePhase TRANSITION_ACTIVATED_COMPLETED = - new DefaultWorkItemLifeCyclePhase("skip", ACTIVATED, COMPLETED, UserTaskKogitoWorkItemHandler::userTaskCompleteWorkItemHandler); - private static final String DESCRIPTION = "Description"; private static final String PRIORITY = "Priority"; private static final String TASK_NAME = "TaskName"; @@ -83,40 +56,16 @@ public class UserTaskKogitoWorkItemHandler extends DefaultKogitoWorkItemHandler private static final String BUSINESSADMINISTRATOR_GROUP_ID = "BusinessAdministratorGroupId"; private static final String EXCLUDED_OWNER_ID = "ExcludedOwnerId"; - @Override - public String getName() { - return "Human Task"; - } - - @Override - public WorkItemLifeCycle initialize() { - return new DefaultWorkItemLifeCycle( - TRANSITION_CREATED_ACTIVE, - TRANSITION_ACTIVATED_CLAIM, - TRANSITION_ACTIVATED_ABORT, - TRANSITION_ACTIVATED_COMPLETE, - TRANSITION_RESERVED_RELEASE, - TRANSITION_RESERVED_ABORT, - TRANSITION_RESERVED_COMPLETE, - TRANSITION_ACTIVATED_COMPLETED); - } - - @Override - public WorkItemTransition startingTransition(Map data, Policy... policies) { - return workItemLifeCycle.newTransition("activate", null, data, policies); - } - - @Override - public WorkItemTransition abortTransition(String phaseStatus, Policy... policies) { - return workItemLifeCycle.newTransition("abort", phaseStatus, emptyMap(), policies); + public UserTaskKogitoWorkItemHandler() { + super(); } - @Override - public WorkItemTransition completeTransition(String phaseStatus, Map data, Policy... policies) { - return workItemLifeCycle.newTransition("complete", phaseStatus, data, policies); + public UserTaskKogitoWorkItemHandler(Application application) { + super(); + this.setApplication(application); } - static public Optional userTaskActivateWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workItem, WorkItemTransition transition) { + public Optional activateWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workItem, WorkItemTransition transition) { UserTasks userTasks = handler.getApplication().get(UserTasks.class); Object priority = workItem.getParameter(PRIORITY); @@ -130,11 +79,11 @@ static public Optional userTaskActivateWorkItemHandler(Kogit UserTask userTask = userTasks.userTaskById((String) workItem.getParameter(KogitoWorkItem.PARAMETER_UNIQUE_TASK_ID)); DefaultUserTaskInstance instance = (DefaultUserTaskInstance) userTask.createInstance(); - instance.setId(workItem.getStringId()); instance.setTaskName((String) workItem.getParameter(TASK_NAME)); instance.setTaskDescription((String) workItem.getParameter(DESCRIPTION)); instance.setTaskPriority(priorityInteger); instance.setExternalReferenceId(workItem.getStringId()); + instance.setMetadata("ProcessId", workItem.getProcessInstance().getProcessId()); instance.setMetadata("ProcessType", workItem.getProcessInstance().getProcess().getType()); instance.setMetadata("ProcessVersion", workItem.getProcessInstance().getProcessVersion()); @@ -144,114 +93,61 @@ static public Optional userTaskActivateWorkItemHandler(Kogit instance.setMetadata("RootProcessInstanceId", workItem.getProcessInstance().getRootProcessInstanceId()); instance.setMetadata("ParentProcessInstanceId", workItem.getProcessInstance().getParentProcessInstanceId()); - ofNullable(workItem.getParameters().get(ACTOR_ID)).map(String.class::cast).map(UserTaskKogitoWorkItemHandler::toSet).ifPresent(instance::setPotentialUsers); - ofNullable(workItem.getParameters().get(GROUP_ID)).map(String.class::cast).map(UserTaskKogitoWorkItemHandler::toSet).ifPresent(instance::setPotentialGroups); - ofNullable(workItem.getParameters().get(BUSINESSADMINISTRATOR_ID)).map(String.class::cast).map(UserTaskKogitoWorkItemHandler::toSet).ifPresent(instance::setAdminUsers); - ofNullable(workItem.getParameters().get(BUSINESSADMINISTRATOR_GROUP_ID)).map(String.class::cast).map(UserTaskKogitoWorkItemHandler::toSet).ifPresent(instance::setAdminGroups); - ofNullable(workItem.getParameters().get(EXCLUDED_OWNER_ID)).map(String.class::cast).map(UserTaskKogitoWorkItemHandler::toSet).ifPresent(instance::setExcludedUsers); + userTask.instances().create(instance); + instance.fireInitialStateChange(); + workItem.getParameters().forEach(instance::setInput); + + ofNullable(workItem.getParameters().get(ACTOR_ID)).map(String.class::cast).map(this::toSet).ifPresent(instance::setPotentialUsers); + ofNullable(workItem.getParameters().get(GROUP_ID)).map(String.class::cast).map(this::toSet).ifPresent(instance::setPotentialGroups); + ofNullable(workItem.getParameters().get(BUSINESSADMINISTRATOR_ID)).map(String.class::cast).map(this::toSet).ifPresent(instance::setAdminUsers); + ofNullable(workItem.getParameters().get(BUSINESSADMINISTRATOR_GROUP_ID)).map(String.class::cast).map(this::toSet).ifPresent(instance::setAdminGroups); + ofNullable(workItem.getParameters().get(EXCLUDED_OWNER_ID)).map(String.class::cast).map(this::toSet).ifPresent(instance::setExcludedUsers); - instance.assign(); - instance.transition(instance.createTransitionToken("activate", emptyMap())); + instance.transition(DefaultUserTaskLifeCycle.ACTIVATE, emptyMap(), IdentityProviders.of(WORKFLOW_ENGINE_USER)); if (workItem instanceof InternalKogitoWorkItem ikw) { ikw.setExternalReferenceId(instance.getId()); ikw.setActualOwner(instance.getActualOwner()); } - userTask.instances().create(instance); - return Optional.empty(); - } - static protected Set toSet(String value) { - if (value == null) { - return null; - } - return Set.of(value.split(UT_SEPARATOR)); + return Optional.empty(); } - static public Optional userTaskClaimWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workItem, WorkItemTransition transition) { - workItem.removeOutput("ACTUAL_OWNER"); - + @Override + public Optional completeWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workItem, WorkItemTransition transition) { UserTasks userTasks = handler.getApplication().get(UserTasks.class); UserTask userTask = userTasks.userTaskById((String) workItem.getParameter(KogitoWorkItem.PARAMETER_UNIQUE_TASK_ID)); userTask.instances().findById(workItem.getExternalReferenceId()).ifPresent(ut -> { - Map data = transition.data(); if (workItem instanceof InternalKogitoWorkItem ikw) { - getUserFromTransition(transition).ifPresent(ikw::setActualOwner); - if (data.containsKey("ACTUAL_OWNER") && ikw.getActualOwner() == null) { - ut.setActuaOwner((String) data.get("ACTUAL_OWNER")); - } - if (ikw.getActualOwner() == null) { - throw new InvalidTransitionException("transition claim does not contain user id"); - } + ikw.setActualOwner(ut.getActualOwner()); } - ut.setActuaOwner(workItem.getActualOwner()); - ut.transition(ut.createTransitionToken("claim", emptyMap())); - userTask.instances().update(ut); + ut.transition(DefaultUserTaskLifeCycle.SKIP, Collections.singletonMap(PARAMETER_NOTIFY, Boolean.FALSE), IdentityProviders.of(WORKFLOW_ENGINE_USER)); }); return Optional.empty(); } - static public Optional userTaskReleaseWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workItem, WorkItemTransition transition) { - if (workItem instanceof InternalKogitoWorkItem ikw) { - ikw.setActualOwner(null); - } - UserTasks userTasks = handler.getApplication().get(UserTasks.class); - UserTask userTask = userTasks.userTaskById((String) workItem.getParameter(KogitoWorkItem.PARAMETER_UNIQUE_TASK_ID)); - userTask.instances().findById(workItem.getExternalReferenceId()).ifPresent(ut -> { - ut.setActuaOwner(null); - ut.transition(ut.createTransitionToken("release", emptyMap())); - userTask.instances().update(ut); - }); - - return Optional.empty(); - } - - static public Optional userTaskCompleteWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workItem, WorkItemTransition transition) { + @Override + public Optional abortWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workItem, WorkItemTransition transition) { UserTasks userTasks = handler.getApplication().get(UserTasks.class); UserTask userTask = userTasks.userTaskById((String) workItem.getParameter(KogitoWorkItem.PARAMETER_UNIQUE_TASK_ID)); userTask.instances().findById(workItem.getExternalReferenceId()).ifPresent(ut -> { - ut.transition(ut.createTransitionToken("complete", emptyMap())); - userTask.instances().update(ut); + if (workItem instanceof InternalKogitoWorkItem ikw) { + ikw.setActualOwner(ut.getActualOwner()); + } + ut.transition(DefaultUserTaskLifeCycle.FAIL, Collections.singletonMap(PARAMETER_NOTIFY, Boolean.FALSE), IdentityProviders.of(WORKFLOW_ENGINE_USER)); }); - if (workItem instanceof InternalKogitoWorkItem ikw && ikw.getActualOwner() == null) { - getUserFromTransition(transition).ifPresent(user -> { - ikw.setActualOwner(user); - }); - } return Optional.empty(); } - static public Optional userTaskCompleteFromActiveWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workItem, - WorkItemTransition transition) { - UserTasks userTasks = handler.getApplication().get(UserTasks.class); - UserTask userTask = userTasks.userTaskById((String) workItem.getParameter(KogitoWorkItem.PARAMETER_UNIQUE_TASK_ID)); - userTask.instances().findById(workItem.getExternalReferenceId()).ifPresent(ut -> { - ut.transition(ut.createTransitionToken("complete", emptyMap())); - userTask.instances().update(ut); - }); - if (workItem instanceof InternalKogitoWorkItem ikw) { - getUserFromTransition(transition).ifPresent(user -> { - ikw.setActualOwner(user); - }); + protected Set toSet(String value) { + if (value == null) { + return null; } - return Optional.empty(); - } - - static public Optional userTaskAbortWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workItem, WorkItemTransition transition) { - UserTasks userTasks = handler.getApplication().get(UserTasks.class); - UserTask userTask = userTasks.userTaskById((String) workItem.getParameter(KogitoWorkItem.PARAMETER_UNIQUE_TASK_ID)); - userTask.instances().findById(workItem.getExternalReferenceId()).ifPresent(ut -> { - ut.transition(ut.createTransitionToken("skip", emptyMap())); - userTask.instances().update(ut); - }); - return Optional.empty(); + return Set.of(value.split(UT_SEPARATOR)); } - private static Optional getUserFromTransition(WorkItemTransition transition) { - Optional securityPolicy = transition.policies().stream().filter(SecurityPolicy.class::isInstance).map(SecurityPolicy.class::cast).findAny(); - if (securityPolicy.isPresent()) { - return Optional.ofNullable(securityPolicy.get().getUser()); - } - return Optional.empty(); + @Override + public String getName() { + return "Human Task"; } } diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandlerProcessListener.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandlerProcessListener.java new file mode 100644 index 00000000000..7d0bc70557a --- /dev/null +++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandlerProcessListener.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.jbpm.usertask.handler; + +import java.util.HashMap; +import java.util.Map; + +import org.kie.kogito.process.Processes; +import org.kie.kogito.usertask.UserTaskEventListener; +import org.kie.kogito.usertask.events.UserTaskStateEvent; +import org.kie.kogito.usertask.impl.lifecycle.DefaultUserTaskLifeCycle; +import org.kie.kogito.usertask.lifecycle.UserTaskState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class UserTaskKogitoWorkItemHandlerProcessListener implements UserTaskEventListener { + + private static final Logger LOG = LoggerFactory.getLogger(UserTaskKogitoWorkItemHandlerProcessListener.class); + + private Processes processes; + + public UserTaskKogitoWorkItemHandlerProcessListener(Processes processes) { + this.processes = processes; + } + + @Override + public void onUserTaskState(UserTaskStateEvent event) { + UserTaskState userTaskState = event.getNewStatus(); + if (!userTaskState.isTerminate()) { + return; + } + + // we check first that the work item is not finished to convey the signal + Boolean notify = (Boolean) event.getUserTaskInstance().getMetadata().get(DefaultUserTaskLifeCycle.PARAMETER_NOTIFY); + if (notify != null && !notify) { + return; + } + + LOG.debug("onUserTaskState {} on complete work item", event); + String processId = (String) event.getUserTaskInstance().getMetadata().get("ProcessId"); + String processInstanceId = (String) event.getUserTaskInstance().getMetadata().get("ProcessInstanceId"); + + processes.processById(processId).instances().findById(processInstanceId).ifPresent(pi -> { + Map data = new HashMap<>(event.getUserTaskInstance().getOutputs()); + data.put("ActorId", event.getUserTaskInstance().getActualOwner()); + pi.completeWorkItem(event.getUserTaskInstance().getExternalReferenceId(), data); + }); + + } +} diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/AbstractUserTask.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/AbstractUserTask.java new file mode 100644 index 00000000000..19a7d8c1e74 --- /dev/null +++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/AbstractUserTask.java @@ -0,0 +1,248 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.usertask.impl; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.kie.kogito.usertask.UserTask; +import org.kie.kogito.usertask.UserTaskInstance; +import org.kie.kogito.usertask.impl.model.DeadlineHelper; +import org.kie.kogito.usertask.model.DeadlineInfo; +import org.kie.kogito.usertask.model.Reassignment; + +public abstract class AbstractUserTask implements UserTask { + private String separator = System.getProperty("org.jbpm.ht.user.separator", ","); + + private String id; + private String name; + private String taskName; + private String taskDescription; + private String referenceName; + private Integer taskPriority; + private Boolean skippable; + private Set potentialUsers; + private Set potentialGroups; + private Set adminUsers; + private Set adminGroups; + private Set excludedUsers; + private Collection>> startDeadlines; + private Collection>> endDeadlines; + private Collection> startReassigments; + private Collection> endReassigments; + + public AbstractUserTask(String id, String name) { + this.id = id; + this.name = name; + this.skippable = Boolean.FALSE; + this.potentialUsers = new HashSet<>(); + this.potentialGroups = new HashSet<>(); + this.adminUsers = new HashSet<>(); + this.adminGroups = new HashSet<>(); + this.excludedUsers = new HashSet<>(); + this.startDeadlines = new HashSet<>(); + this.endDeadlines = new HashSet<>(); + this.startReassigments = new HashSet<>(); + this.endReassigments = new HashSet<>(); + } + + @Override + public UserTaskInstance createInstance() { + DefaultUserTaskInstance instance = new DefaultUserTaskInstance(this); + instance.setUserTaskId(id()); + instance.setTaskName(getTaskName()); + instance.setTaskDescription(getTaskDescription()); + instance.setTaskPriority(getTaskPriority()); + instance.setPotentialUsers(getPotentialUsers()); + instance.setPotentialGroups(getPotentialGroups()); + instance.setAdminUsers(getAdminUsers()); + instance.setPotentialGroups(getPotentialGroups()); + instance.setExcludedUsers(getExcludedUsers()); + return instance; + } + + @Override + public String id() { + return id; + } + + @Override + public String name() { + return name; + } + + public void setSkippable(String skippable) { + this.skippable = Boolean.parseBoolean(skippable); + } + + public Boolean getSkippable() { + return skippable; + } + + @Override + public String getTaskName() { + return taskName; + } + + public void setTaskName(String taskName) { + this.taskName = taskName; + } + + @Override + public String getTaskDescription() { + return taskDescription; + } + + public void setTaskDescription(String taskDescription) { + this.taskDescription = taskDescription; + } + + @Override + public Integer getTaskPriority() { + return this.taskPriority; + } + + public void setTaskPriority(Integer taskPriority) { + this.taskPriority = taskPriority; + } + + public void setReferenceName(String referenceName) { + this.referenceName = referenceName; + } + + @Override + public String getReferenceName() { + return referenceName; + } + + @Override + public Set getPotentialUsers() { + return this.potentialUsers; + } + + public void setPotentialUsers(String potentialUsers) { + this.setPotentialUsers(toSet(potentialUsers)); + } + + public void setPotentialUsers(Set potentialUsers) { + this.potentialUsers = potentialUsers; + } + + @Override + public Set getPotentialGroups() { + return this.potentialGroups; + } + + public void setPotentialGroups(String potentialGroups) { + this.setPotentialGroups(toSet(potentialGroups)); + } + + public void setPotentialGroups(Set potentialGroups) { + this.potentialGroups = potentialGroups; + } + + @Override + public Set getAdminUsers() { + return this.adminUsers; + } + + public void setAdminUsers(String adminUsers) { + this.setAdminUsers(toSet(adminUsers)); + } + + public void setAdminUsers(Set adminUsers) { + this.adminUsers = adminUsers; + } + + @Override + public Set getAdminGroups() { + return this.adminGroups; + } + + public void setAdminGroups(String adminGroups) { + this.setAdminGroups(toSet(adminGroups)); + } + + public void setAdminGroups(Set adminGroups) { + this.adminGroups = adminGroups; + } + + @Override + public Set getExcludedUsers() { + return this.excludedUsers; + } + + public void setExcludedUsers(String excludedUsers) { + this.setExcludedUsers(toSet(excludedUsers)); + } + + public void setExcludedUsers(Set excludedUsers) { + this.excludedUsers = excludedUsers; + } + + @Override + public Collection>> getNotStartedDeadlines() { + return startDeadlines; + } + + public void setNotStartedDeadLines(String deadlines) { + this.startDeadlines = DeadlineHelper.parseDeadlines(deadlines); + } + + @Override + public Collection>> getNotCompletedDeadlines() { + return endDeadlines; + } + + public void setNotCompletedDeadlines(String notStarted) { + this.endDeadlines = DeadlineHelper.parseDeadlines(notStarted); + } + + @Override + public Collection> getNotStartedReassignments() { + return startReassigments; + } + + public void setNotStartedReassignments(String reassignments) { + this.startReassigments = DeadlineHelper.parseReassignments(reassignments); + } + + @Override + public Collection> getNotCompletedReassigments() { + return endReassigments; + } + + public void setNotCompletedReassigments(String reassignments) { + this.endReassigments = DeadlineHelper.parseReassignments(reassignments); + } + + protected Set toSet(String value) { + if (value == null) { + return new HashSet<>(); + } + Set store = new HashSet<>(); + for (String item : value.split(separator)) { + store.add(item); + } + return store; + } + +} diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/BasicUserTaskAssignmentStrategy.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/BasicUserTaskAssignmentStrategy.java new file mode 100644 index 00000000000..076a62c313e --- /dev/null +++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/BasicUserTaskAssignmentStrategy.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.usertask.impl; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import org.kie.kogito.auth.IdentityProvider; +import org.kie.kogito.usertask.UserTaskAssignmentStrategy; +import org.kie.kogito.usertask.UserTaskInstance; + +public class BasicUserTaskAssignmentStrategy implements UserTaskAssignmentStrategy { + + @Override + public String getName() { + return DEFAULT_NAME; + } + + @Override + public Optional computeAssigment(UserTaskInstance userTaskInstance, IdentityProvider identityProvider) { + Set users = new HashSet<>(userTaskInstance.getPotentialUsers()); + users.removeAll(userTaskInstance.getExcludedUsers()); + if (users.size() == 1) { + return Optional.of(users.iterator().next()); + } + return Optional.empty(); + } + +} diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTask.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTask.java index 92e2fa9c745..9b80e6e7176 100644 --- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTask.java +++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTask.java @@ -18,115 +18,30 @@ */ package org.kie.kogito.usertask.impl; -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - import org.kie.kogito.Application; -import org.kie.kogito.uow.events.UnitOfWorkUserTaskEventListener; -import org.kie.kogito.usertask.UserTask; +import org.kie.kogito.usertask.UserTaskAssignmentStrategy; import org.kie.kogito.usertask.UserTaskConfig; -import org.kie.kogito.usertask.UserTaskInstance; import org.kie.kogito.usertask.UserTaskInstances; -import org.kie.kogito.usertask.impl.model.DeadlineHelper; -import org.kie.kogito.usertask.model.DeadlineInfo; -import org.kie.kogito.usertask.model.Reassignment; - -public class DefaultUserTask implements UserTask { - private String separator = System.getProperty("org.jbpm.ht.user.separator", ","); +public class DefaultUserTask extends AbstractUserTask { private Application application; - private String id; private UserTaskInstances userTaskInstances; - private String name; - private String taskName; - private String taskDescription; - private String referenceName; - private Integer taskPriority; - private Boolean skippable; - private Set potentialUsers; - private Set potentialGroups; - private Set adminUsers; - private Set adminGroups; - private Set excludedUsers; - private Collection>> startDeadlines; - private Collection>> endDeadlines; - private Collection> startReassigments; - private Collection> endReassigments; - - public DefaultUserTask() { - // nothing - } public DefaultUserTask(String id, String name) { - this(null, id, name, new InMemoryUserTaskInstances()); + super(id, name); } public DefaultUserTask(Application application, String id, String name) { - this(application, id, name, new InMemoryUserTaskInstances()); - } - - public DefaultUserTask(Application application, String id, String name, UserTaskInstances userTaskInstances) { - this.application = application; - this.id = id; - this.name = name; - this.userTaskInstances = userTaskInstances; - this.userTaskInstances.setReconnectUserTaskInstance(this::connect); - this.userTaskInstances.setDisconnectUserTaskInstance(this::disconnect); - this.skippable = Boolean.FALSE; - this.potentialUsers = new HashSet<>(); - this.potentialGroups = new HashSet<>(); - this.adminUsers = new HashSet<>(); - this.adminGroups = new HashSet<>(); - this.excludedUsers = new HashSet<>(); - this.startDeadlines = new HashSet<>(); - this.endDeadlines = new HashSet<>(); - this.startReassigments = new HashSet<>(); - this.endReassigments = new HashSet<>(); - - } - - public void setApplication(Application application) { + super(id, name); this.application = application; + this.userTaskInstances = application.config().get(UserTaskConfig.class).userTaskInstances(); } @Override - public UserTaskInstance createInstance() { - DefaultUserTaskInstance instance = new DefaultUserTaskInstance(this); - instance.setTaskName(getTaskName()); - instance.setTaskDescription(getTaskDescription()); - instance.setTaskPriority(getTaskPriority()); - instance.setPotentialUsers(getPotentialUsers()); - instance.setPotentialGroups(getPotentialGroups()); - instance.setAdminUsers(getAdminUsers()); - instance.setPotentialGroups(getPotentialGroups()); - instance.setExcludedUsers(getExcludedUsers()); - connect(instance); - return instance; - } - - private UserTaskInstance disconnect(UserTaskInstance userTaskInstance) { - DefaultUserTaskInstance instance = (DefaultUserTaskInstance) userTaskInstance; - instance.setUserTask(null); - instance.setUserTaskEventSupport(null); - instance.setUserTaskLifeCycle(null); - instance.setInstances(null); - return instance; - } - - public UserTaskInstance connect(UserTaskInstance userTaskInstance) { - DefaultUserTaskInstance instance = (DefaultUserTaskInstance) userTaskInstance; + public UserTaskAssignmentStrategy getAssignmentStrategy() { UserTaskConfig userTaskConfig = application.config().get(UserTaskConfig.class); - KogitoUserTaskEventSupportImpl impl = new KogitoUserTaskEventSupportImpl(userTaskConfig.identityProvider()); - userTaskConfig.userTaskEventListeners().listeners().forEach(impl::addEventListener); - impl.addEventListener(new UnitOfWorkUserTaskEventListener(application.unitOfWorkManager())); - instance.setUserTask(this); - instance.setUserTaskEventSupport(impl); - instance.setUserTaskLifeCycle(userTaskConfig.userTaskLifeCycle()); - instance.setInstances(userTaskInstances); - return instance; + return userTaskConfig.userTaskAssignmentStrategies().defaultUserTaskAssignmentStrategy(); } @Override @@ -134,170 +49,4 @@ public UserTaskInstances instances() { return userTaskInstances; } - @Override - public String id() { - return id; - } - - @Override - public String name() { - return name; - } - - public void setSkippable(String skippable) { - this.skippable = Boolean.parseBoolean(skippable); - } - - public Boolean getSkippable() { - return skippable; - } - - @Override - public String getTaskName() { - return taskName; - } - - public void setTaskName(String taskName) { - this.taskName = taskName; - } - - @Override - public String getTaskDescription() { - return taskDescription; - } - - public void setTaskDescription(String taskDescription) { - this.taskDescription = taskDescription; - } - - @Override - public Integer getTaskPriority() { - return this.taskPriority; - } - - public void setTaskPriority(Integer taskPriority) { - this.taskPriority = taskPriority; - } - - public void setReferenceName(String referenceName) { - this.referenceName = referenceName; - } - - @Override - public String getReferenceName() { - return referenceName; - } - - @Override - public Set getPotentialUsers() { - return this.potentialUsers; - } - - public void setPotentialUsers(String potentialUsers) { - this.setPotentialUsers(toSet(potentialUsers)); - } - - public void setPotentialUsers(Set potentialUsers) { - this.potentialUsers = potentialUsers; - } - - @Override - public Set getPotentialGroups() { - return this.potentialGroups; - } - - public void setPotentialGroups(String potentialGroups) { - this.setPotentialGroups(toSet(potentialGroups)); - } - - public void setPotentialGroups(Set potentialGroups) { - this.potentialGroups = potentialGroups; - } - - @Override - public Set getAdminUsers() { - return this.adminUsers; - } - - public void setAdminUsers(String adminUsers) { - this.setAdminUsers(toSet(adminUsers)); - } - - public void setAdminUsers(Set adminUsers) { - this.adminUsers = adminUsers; - } - - @Override - public Set getAdminGroups() { - return this.adminGroups; - } - - public void setAdminGroups(String adminGroups) { - this.setAdminGroups(toSet(adminGroups)); - } - - public void setAdminGroups(Set adminGroups) { - this.adminGroups = adminGroups; - } - - @Override - public Set getExcludedUsers() { - return this.excludedUsers; - } - - public void setExcludedUsers(String excludedUsers) { - this.setExcludedUsers(toSet(excludedUsers)); - } - - public void setExcludedUsers(Set excludedUsers) { - this.excludedUsers = excludedUsers; - } - - @Override - public Collection>> getNotStartedDeadlines() { - return startDeadlines; - } - - public void setNotStartedDeadLines(String deadlines) { - this.startDeadlines = DeadlineHelper.parseDeadlines(deadlines); - } - - @Override - public Collection>> getNotCompletedDeadlines() { - return endDeadlines; - } - - public void setNotCompletedDeadlines(String notStarted) { - this.endDeadlines = DeadlineHelper.parseDeadlines(notStarted); - } - - @Override - public Collection> getNotStartedReassignments() { - return startReassigments; - } - - public void setNotStartedReassignments(String reassignments) { - this.startReassigments = DeadlineHelper.parseReassignments(reassignments); - } - - @Override - public Collection> getNotCompletedReassigments() { - return endReassigments; - } - - public void setNotCompletedReassigments(String reassignments) { - this.endReassigments = DeadlineHelper.parseReassignments(reassignments); - } - - protected Set toSet(String value) { - if (value == null) { - return new HashSet<>(); - } - Set store = new HashSet<>(); - for (String item : value.split(separator)) { - store.add(item); - } - return store; - } - } diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskAssignmentStrategyConfig.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskAssignmentStrategyConfig.java new file mode 100644 index 00000000000..5c6029b4778 --- /dev/null +++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskAssignmentStrategyConfig.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.usertask.impl; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.kie.kogito.usertask.UserTaskAssignmentStrategy; +import org.kie.kogito.usertask.UserTaskAssignmentStrategyConfig; + +public class DefaultUserTaskAssignmentStrategyConfig implements UserTaskAssignmentStrategyConfig { + + private Map userTaskAssignmentStrategies; + + public DefaultUserTaskAssignmentStrategyConfig() { + this.userTaskAssignmentStrategies = new HashMap<>(); + BasicUserTaskAssignmentStrategy strategy = new BasicUserTaskAssignmentStrategy(); + this.userTaskAssignmentStrategies.put(strategy.getName(), strategy); + } + + public DefaultUserTaskAssignmentStrategyConfig(Iterable userTaskAssignmentStrategies) { + this(); + userTaskAssignmentStrategies.forEach(uts -> this.userTaskAssignmentStrategies.put(uts.getName(), uts)); + } + + @Override + public List userTaskAssignmentStrategies() { + return new ArrayList<>(userTaskAssignmentStrategies.values()); + } + + @Override + public UserTaskAssignmentStrategy forName(String name) { + return userTaskAssignmentStrategies.get(name); + } + +} diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskConfig.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskConfig.java index 93d012f0116..7282ce72532 100644 --- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskConfig.java +++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskConfig.java @@ -27,8 +27,10 @@ import org.kie.kogito.services.uow.CollectingUnitOfWorkFactory; import org.kie.kogito.services.uow.DefaultUnitOfWorkManager; import org.kie.kogito.uow.UnitOfWorkManager; +import org.kie.kogito.usertask.UserTaskAssignmentStrategyConfig; import org.kie.kogito.usertask.UserTaskConfig; import org.kie.kogito.usertask.UserTaskEventListenerConfig; +import org.kie.kogito.usertask.UserTaskInstances; import org.kie.kogito.usertask.impl.lifecycle.DefaultUserTaskLifeCycle; import org.kie.kogito.usertask.lifecycle.UserTaskLifeCycle; @@ -39,13 +41,17 @@ public class DefaultUserTaskConfig implements UserTaskConfig { private JobsService jobService; private IdentityProvider identityProvider; private UserTaskLifeCycle userTaskLifeCycle; + private UserTaskAssignmentStrategyConfig userTaskAssignmentStrategyConfig; + private UserTaskInstances userTaskInstances; public DefaultUserTaskConfig() { this(new DefaultUserTaskEventListenerConfig(), new DefaultUnitOfWorkManager(new CollectingUnitOfWorkFactory()), null, new NoOpIdentityProvider(), - new DefaultUserTaskLifeCycle()); + new DefaultUserTaskLifeCycle(), + new DefaultUserTaskAssignmentStrategyConfig(), + new InMemoryUserTaskInstances()); } public DefaultUserTaskConfig( @@ -53,13 +59,17 @@ public DefaultUserTaskConfig( Iterable unitOfWorkManager, Iterable jobService, Iterable identityProvider, - Iterable userTaskLifeCycle) { + Iterable userTaskLifeCycle, + Iterable userTaskAssignmentStrategyConfig, + Iterable userTaskInstances) { this.userTaskEventListeners = singleton(userTaskEventListenerConfig, DefaultUserTaskEventListenerConfig::new); this.unitOfWorkManager = singleton(unitOfWorkManager, () -> new DefaultUnitOfWorkManager(new CollectingUnitOfWorkFactory())); this.jobService = singleton(jobService, () -> null); this.identityProvider = singleton(identityProvider, NoOpIdentityProvider::new); this.userTaskLifeCycle = singleton(userTaskLifeCycle, DefaultUserTaskLifeCycle::new); + this.userTaskAssignmentStrategyConfig = singleton(userTaskAssignmentStrategyConfig, DefaultUserTaskAssignmentStrategyConfig::new); + this.userTaskInstances = singleton(userTaskInstances, InMemoryUserTaskInstances::new); } @@ -76,12 +86,16 @@ public DefaultUserTaskConfig( UnitOfWorkManager unitOfWorkManager, JobsService jobService, IdentityProvider identityProvider, - UserTaskLifeCycle userTaskLifeCycle) { + UserTaskLifeCycle userTaskLifeCycle, + DefaultUserTaskAssignmentStrategyConfig userTaskAssignmentStrategyConfig, + UserTaskInstances userTaskInstances) { this.userTaskEventListeners = userTaskEventListenerConfig; this.unitOfWorkManager = unitOfWorkManager; this.jobService = jobService; this.identityProvider = identityProvider; this.userTaskLifeCycle = userTaskLifeCycle; + this.userTaskAssignmentStrategyConfig = userTaskAssignmentStrategyConfig; + this.userTaskInstances = userTaskInstances; } @Override @@ -109,4 +123,14 @@ public UserTaskLifeCycle userTaskLifeCycle() { return userTaskLifeCycle; } + @Override + public UserTaskAssignmentStrategyConfig userTaskAssignmentStrategies() { + return userTaskAssignmentStrategyConfig; + } + + @Override + public UserTaskInstances userTaskInstances() { + return userTaskInstances; + } + } diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskEventListenerConfig.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskEventListenerConfig.java index 9cb7f3ac09e..086c729124f 100644 --- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskEventListenerConfig.java +++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskEventListenerConfig.java @@ -32,8 +32,13 @@ public DefaultUserTaskEventListenerConfig() { this.listeners = new ArrayList<>(); } + public DefaultUserTaskEventListenerConfig(Iterable listeners) { + this(); + listeners.forEach(this::addUserTaskEventListener); + } + public DefaultUserTaskEventListenerConfig(List listeners) { - this.listeners = new ArrayList<>(listeners); + this((Iterable) listeners); } public void addUserTaskEventListener(UserTaskEventListener userTaskEventListener) { diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskInstance.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskInstance.java index 8adb9f07d02..e5265ae3f7f 100644 --- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskInstance.java +++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTaskInstance.java @@ -19,7 +19,7 @@ package org.kie.kogito.usertask.impl; import java.util.ArrayList; -import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -28,6 +28,7 @@ import java.util.Set; import java.util.UUID; +import org.kie.kogito.auth.IdentityProvider; import org.kie.kogito.internal.usertask.event.KogitoUserTaskEventSupport; import org.kie.kogito.internal.usertask.event.KogitoUserTaskEventSupport.AssignmentType; import org.kie.kogito.usertask.UserTask; @@ -35,7 +36,6 @@ import org.kie.kogito.usertask.UserTaskInstances; import org.kie.kogito.usertask.lifecycle.UserTaskLifeCycle; import org.kie.kogito.usertask.lifecycle.UserTaskState; -import org.kie.kogito.usertask.lifecycle.UserTaskTransition; import org.kie.kogito.usertask.lifecycle.UserTaskTransitionToken; import org.kie.kogito.usertask.model.Attachment; import org.kie.kogito.usertask.model.Comment; @@ -46,6 +46,8 @@ public class DefaultUserTaskInstance implements UserTaskInstance { private String id; + private String userTaskId; + private UserTaskState status; private String actualOwner; private String taskName; @@ -75,6 +77,9 @@ public class DefaultUserTaskInstance implements UserTaskInstance { private UserTaskLifeCycle setUserTaskLifeCycle; public DefaultUserTaskInstance() { + this.inputs = new HashMap<>(); + this.outputs = new HashMap<>(); + this.status = UserTaskState.initalized(); this.metadata = new HashMap<>(); this.attachments = new ArrayList<>(); this.comments = new ArrayList<>(); @@ -86,27 +91,10 @@ public DefaultUserTaskInstance() { } public DefaultUserTaskInstance(UserTask userTask) { + this(); this.id = UUID.randomUUID().toString(); this.userTask = userTask; this.instances = userTask.instances(); - this.status = UserTaskState.initalized(); - this.metadata = new HashMap<>(); - this.attachments = new ArrayList<>(); - this.comments = new ArrayList<>(); - this.potentialUsers = new HashSet<>(); - this.potentialGroups = new HashSet<>(); - this.adminUsers = new HashSet<>(); - this.adminGroups = new HashSet<>(); - this.excludedUsers = new HashSet<>(); - } - - public void assign() { - Set potentialUsers = new HashSet<>(this.getPotentialUsers()); - potentialUsers.removeAll(getExcludedUsers()); - - if (potentialUsers.size() == 1) { - this.actualOwner = potentialUsers.iterator().next(); - } } public void setUserTaskEventSupport(KogitoUserTaskEventSupport userTaskEventSupport) { @@ -121,22 +109,17 @@ public void setInstances(UserTaskInstances instances) { this.instances = instances; } - @Override - public void complete() { - UserTaskTransitionToken transition = this.setUserTaskLifeCycle.newCompleteTransitionToken(this, Collections.emptyMap()); - transition(transition); - instances.remove(id); + public void setId(String id) { + this.id = id; } @Override - public void abort() { - UserTaskTransitionToken transition = this.setUserTaskLifeCycle.newAbortTransitionToken(this, Collections.emptyMap()); - transition(transition); - instances.remove(id); + public String getUserTaskId() { + return userTaskId; } - public void setId(String id) { - this.id = id; + public void setUserTaskId(String userTaskId) { + this.userTaskId = userTaskId; } @Override @@ -162,8 +145,9 @@ public boolean hasActualOwner() { public void setActuaOwner(String actualOwner) { this.actualOwner = actualOwner; if (this.userTaskEventSupport != null) { - this.userTaskEventSupport.fireOneUserTaskStateChange(this, this.status.getName(), this.status.getName()); + this.userTaskEventSupport.fireOneUserTaskStateChange(this, this.status, this.status); } + updatePersistence(); } @Override @@ -181,21 +165,29 @@ public void setExternalReferenceId(String externalReferenceId) { } @Override - public UserTaskTransitionToken createTransitionToken(String transitionId, Map data) { - return this.setUserTaskLifeCycle.newTransitionToken(transitionId, this, data); - } - - @Override - public void transition(UserTaskTransitionToken token) { - Optional next = Optional.of(token); + public void transition(String transitionId, Map data, IdentityProvider identity) { + Optional next = Optional.of(this.setUserTaskLifeCycle.newTransitionToken(transitionId, this, data)); while (next.isPresent()) { - UserTaskTransition transition = next.get().transition(); - next = this.setUserTaskLifeCycle.transition(this, token); + UserTaskTransitionToken transition = next.get(); + next = this.setUserTaskLifeCycle.transition(this, transition, identity); this.status = transition.target(); - this.userTaskEventSupport.fireOneUserTaskStateChange(this, transition.source().getName(), transition.target().getName()); - if (this.status.isTerminate().isPresent()) { - this.instances.remove(this.id); - } + this.updatePersistenceOrRemove(); + this.userTaskEventSupport.fireOneUserTaskStateChange(this, transition.source(), transition.target()); + } + + } + + private void updatePersistence() { + if (this.instances != null) { + this.instances.update(this); + } + } + + private void updatePersistenceOrRemove() { + if (this.status.isTerminate()) { + this.instances.remove(this.id); + } else { + this.instances.update(this); } } @@ -204,7 +196,7 @@ public UserTask getUserTask() { return userTask; } - public void setUserTask(DefaultUserTask userTask) { + public void setUserTask(UserTask userTask) { this.userTask = userTask; } @@ -213,7 +205,7 @@ public Map getInputs() { } public void setInputs(Map inputs) { - this.inputs = inputs; + inputs.forEach(this::setInput); } public Map getOutputs() { @@ -221,7 +213,25 @@ public Map getOutputs() { } public void setOutputs(Map outputs) { - this.outputs = outputs; + outputs.forEach(this::setOutput); + } + + @Override + public void setInput(String key, Object newValue) { + Object oldValue = this.inputs.put(key, newValue); + if (this.userTaskEventSupport != null) { + this.userTaskEventSupport.fireOnUserTaskInputVariableChange(this, key, oldValue, newValue); + } + updatePersistence(); + } + + @Override + public void setOutput(String key, Object newValue) { + Object oldValue = this.outputs.put(key, newValue); + if (this.userTaskEventSupport != null) { + this.userTaskEventSupport.fireOnUserTaskOutputVariableChange(this, key, oldValue, newValue); + } + updatePersistence(); } /** @@ -237,7 +247,14 @@ public String getTaskName() { public void setTaskName(String taskName) { this.taskName = taskName; if (this.userTaskEventSupport != null) { - this.userTaskEventSupport.fireOneUserTaskStateChange(this, this.status.getName(), this.status.getName()); + this.userTaskEventSupport.fireOneUserTaskStateChange(this, this.status, this.status); + } + updatePersistence(); + } + + public void fireInitialStateChange() { + if (this.userTaskEventSupport != null) { + this.userTaskEventSupport.fireOneUserTaskStateChange(this, this.status, this.status); } } @@ -254,8 +271,9 @@ public String getTaskDescription() { public void setTaskDescription(String taskDescription) { this.taskDescription = taskDescription; if (this.userTaskEventSupport != null) { - this.userTaskEventSupport.fireOneUserTaskStateChange(this, this.status.getName(), this.status.getName()); + this.userTaskEventSupport.fireOneUserTaskStateChange(this, this.status, this.status); } + updatePersistence(); } /** @@ -271,8 +289,9 @@ public Integer getTaskPriority() { public void setTaskPriority(Integer taskPriority) { this.taskPriority = taskPriority; if (this.userTaskEventSupport != null) { - this.userTaskEventSupport.fireOneUserTaskStateChange(this, this.status.getName(), this.status.getName()); + this.userTaskEventSupport.fireOneUserTaskStateChange(this, this.status, this.status); } + updatePersistence(); } /** @@ -291,6 +310,7 @@ public void setPotentialUsers(Set potentialUsers) { if (this.userTaskEventSupport != null) { this.userTaskEventSupport.fireOnUserTaskAssignmentChange(this, AssignmentType.USER_OWNERS, oldValues, potentialUsers); } + updatePersistence(); } /** @@ -309,6 +329,7 @@ public void setPotentialGroups(Set potentialGroups) { if (this.userTaskEventSupport != null) { this.userTaskEventSupport.fireOnUserTaskAssignmentChange(this, AssignmentType.USER_GROUPS, oldValues, potentialGroups); } + updatePersistence(); } /** @@ -327,6 +348,7 @@ public void setAdminUsers(Set adminUsers) { if (this.userTaskEventSupport != null) { this.userTaskEventSupport.fireOnUserTaskAssignmentChange(this, AssignmentType.ADMIN_USERS, oldValues, adminUsers); } + updatePersistence(); } /** @@ -345,6 +367,7 @@ public void setAdminGroups(Set adminGroups) { if (this.userTaskEventSupport != null) { this.userTaskEventSupport.fireOnUserTaskAssignmentChange(this, AssignmentType.ADMIN_GROUPS, oldValues, adminGroups); } + updatePersistence(); } /** @@ -363,6 +386,7 @@ public void setExcludedUsers(Set excludedUsers) { if (this.userTaskEventSupport != null) { this.userTaskEventSupport.fireOnUserTaskAssignmentChange(this, AssignmentType.USERS_EXCLUDED, oldValues, excludedUsers); } + updatePersistence(); } /** @@ -370,41 +394,64 @@ public void setExcludedUsers(Set excludedUsers) { * * @return A map which key is the attachment id and value the attachment object */ + public List getAttachments() { return attachments; } @Override - public void addAttachment(Attachment attachment) { + public Attachment addAttachment(Attachment attachment) { + attachment.setId(UUID.randomUUID().toString()); + attachment.setUpdatedAt(new Date()); this.attachments.add(attachment); if (this.userTaskEventSupport != null) { this.userTaskEventSupport.fireOnUserTaskAttachmentAdded(this, attachment); } + updatePersistence(); + return attachment; } @Override - public void updateAttachment(Attachment newAttachment) { + public Attachment updateAttachment(Attachment newAttachment) { Optional oldAttachment = this.attachments.stream().filter(e -> e.getId().equals(newAttachment.getId())).findFirst(); if (oldAttachment.isEmpty()) { - return; + return null; } this.attachments.remove(oldAttachment.get()); + if (newAttachment.getName() == null) { + String path = newAttachment.getContent().getPath(); + int idx = path.lastIndexOf("/"); + if (idx > 0) { + path = path.substring(idx + 1); + } + newAttachment.setName(path); + } + newAttachment.setUpdatedAt(new Date()); this.attachments.add(newAttachment); if (this.userTaskEventSupport != null) { this.userTaskEventSupport.fireOnUserTaskAttachmentChange(this, oldAttachment.get(), newAttachment); } + updatePersistence(); + return newAttachment; } @Override - public void removeAttachment(Attachment oldAttachment) { - this.attachments.remove(oldAttachment); + public Attachment removeAttachment(Attachment attachment) { + Optional oldAttachment = this.attachments.stream().filter(e -> e.getId().equals(attachment.getId())).findFirst(); + if (oldAttachment.isEmpty()) { + return null; + } + this.attachments.remove(attachment); if (this.userTaskEventSupport != null) { - this.userTaskEventSupport.fireOnUserTaskAttachmentDeleted(this, oldAttachment); + this.userTaskEventSupport.fireOnUserTaskAttachmentDeleted(this, oldAttachment.get()); } + updatePersistence(); + return oldAttachment.get(); } public void setAttachments(List attachments) { this.attachments = attachments; + updatePersistence(); } @@ -413,45 +460,61 @@ public void setAttachments(List attachments) { * * @return A map which key is the comment id and value the comment object */ + public List getComments() { return comments; } @Override - public void addComment(Comment comment) { + public Comment addComment(Comment comment) { + comment.setId(UUID.randomUUID().toString()); + comment.setUpdatedAt(new Date()); this.comments.add(comment); if (this.userTaskEventSupport != null) { this.userTaskEventSupport.fireOnUserTaskCommentAdded(this, comment); } + updatePersistence(); + return comment; } @Override - public void updateComment(Comment newComment) { + public Comment updateComment(Comment newComment) { Optional oldComment = this.comments.stream().filter(e -> e.getId().equals(newComment.getId())).findFirst(); if (oldComment.isEmpty()) { - return; + return null; } this.comments.remove(oldComment.get()); + newComment.setUpdatedAt(new Date()); this.comments.add(newComment); if (this.userTaskEventSupport != null) { this.userTaskEventSupport.fireOnUserTaskCommentChange(this, oldComment.get(), newComment); } + updatePersistence(); + return newComment; } @Override - public void removeComment(Comment comment) { + public Comment removeComment(Comment comment) { + Optional oldComment = this.comments.stream().filter(e -> e.getId().equals(comment.getId())).findFirst(); + if (oldComment.isEmpty()) { + return null; + } this.comments.remove(comment); if (this.userTaskEventSupport != null) { - this.userTaskEventSupport.fireOnUserTaskCommentDeleted(this, comment); + this.userTaskEventSupport.fireOnUserTaskCommentDeleted(this, oldComment.get()); } + updatePersistence(); + return oldComment.get(); } public void setComments(List comments) { this.comments = comments; + updatePersistence(); } public void setMetadata(String key, Object value) { this.metadata.put(key, value); + updatePersistence(); } public Map getMetadata() { @@ -460,6 +523,7 @@ public Map getMetadata() { public void setMetadata(Map metadata) { this.metadata = metadata; + updatePersistence(); } @Override diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTasks.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTasks.java index a2a664e82d8..e2918673324 100644 --- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTasks.java +++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/DefaultUserTasks.java @@ -25,13 +25,18 @@ import java.util.Map; import org.kie.kogito.Application; +import org.kie.kogito.uow.events.UnitOfWorkUserTaskEventListener; import org.kie.kogito.usertask.UserTask; +import org.kie.kogito.usertask.UserTaskConfig; +import org.kie.kogito.usertask.UserTaskInstance; +import org.kie.kogito.usertask.UserTaskInstances; import org.kie.kogito.usertask.UserTasks; public class DefaultUserTasks implements UserTasks { private Map userTasks; private Application application; + private UserTaskInstances userTaskInstances; public DefaultUserTasks() { this.userTasks = new HashMap<>(); @@ -49,6 +54,9 @@ public DefaultUserTasks(Application application, Iterable userTasks) { UserTask userTask = userTaskIterator.next(); this.userTasks.put(userTask.id(), userTask); } + userTaskInstances = application.config().get(UserTaskConfig.class).userTaskInstances(); + userTaskInstances.setDisconnectUserTaskInstance(this::disconnect); + userTaskInstances.setReconnectUserTaskInstance(this::connect); } @Override @@ -61,4 +69,30 @@ public Collection userTaskIds() { return userTasks.keySet(); } + @Override + public UserTaskInstances instances() { + return userTaskInstances; + } + + public UserTaskInstance disconnect(UserTaskInstance userTaskInstance) { + DefaultUserTaskInstance instance = (DefaultUserTaskInstance) userTaskInstance; + instance.setUserTask(null); + instance.setUserTaskEventSupport(null); + instance.setUserTaskLifeCycle(null); + instance.setInstances(null); + return instance; + } + + public UserTaskInstance connect(UserTaskInstance userTaskInstance) { + DefaultUserTaskInstance instance = (DefaultUserTaskInstance) userTaskInstance; + UserTaskConfig userTaskConfig = application.config().get(UserTaskConfig.class); + KogitoUserTaskEventSupportImpl impl = new KogitoUserTaskEventSupportImpl(userTaskConfig.identityProvider()); + userTaskConfig.userTaskEventListeners().listeners().forEach(impl::addEventListener); + impl.addEventListener(new UnitOfWorkUserTaskEventListener(application.unitOfWorkManager())); + instance.setUserTask(application.get(UserTasks.class).userTaskById(instance.getUserTaskId())); + instance.setUserTaskEventSupport(impl); + instance.setUserTaskLifeCycle(userTaskConfig.userTaskLifeCycle()); + instance.setInstances(userTaskInstances); + return instance; + } } diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/InMemoryUserTaskInstances.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/InMemoryUserTaskInstances.java index 6704fbd83a8..def2088da39 100644 --- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/InMemoryUserTaskInstances.java +++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/InMemoryUserTaskInstances.java @@ -18,28 +18,42 @@ */ package org.kie.kogito.usertask.impl; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.function.Function; +import org.kie.kogito.auth.IdentityProvider; import org.kie.kogito.usertask.UserTaskInstance; import org.kie.kogito.usertask.UserTaskInstances; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class InMemoryUserTaskInstances implements UserTaskInstances { + private static Logger LOG = LoggerFactory.getLogger(InMemoryUserTaskInstances.class); + private Map userTaskInstances; private Function reconnectUserTaskInstance; private Function disconnectUserTaskInstance; private ObjectMapper mapper; public InMemoryUserTaskInstances() { + LOG.info("Initializing InMemoryUsertaskInstances"); this.userTaskInstances = new HashMap<>(); - this.reconnectUserTaskInstance = Function.identity(); - this.disconnectUserTaskInstance = Function.identity(); + this.reconnectUserTaskInstance = null; + this.disconnectUserTaskInstance = null; this.mapper = new ObjectMapper(); + this.mapper.registerModule(new JavaTimeModule()); } @Override @@ -55,13 +69,68 @@ public void setDisconnectUserTaskInstance(Function findById(String userTaskInstanceId) { try { + if (!userTaskInstances.containsKey(userTaskInstanceId)) { + return Optional.empty(); + } UserTaskInstance userTaskInstance = mapper.readValue(userTaskInstances.get(userTaskInstanceId), DefaultUserTaskInstance.class); return Optional.ofNullable(reconnectUserTaskInstance.apply(userTaskInstance)); } catch (Exception e) { + LOG.error("during find by Id {}", userTaskInstanceId, e); return Optional.empty(); } } + @Override + public List findByIdentity(IdentityProvider identity) { + try { + String user = identity.getName(); + Collection roles = identity.getRoles(); + List users = new ArrayList<>(); + for (String id : userTaskInstances.keySet()) { + UserTaskInstance userTaskInstance = mapper.readValue(userTaskInstances.get(id), DefaultUserTaskInstance.class); + if (checkVisibility(userTaskInstance, user, roles)) { + users.add(reconnectUserTaskInstance.apply(userTaskInstance)); + } + } + return users; + } catch (Exception e) { + LOG.error("during find by Identity {}", identity.getName(), e); + return Collections.emptyList(); + } + } + + private boolean checkVisibility(UserTaskInstance userTaskInstance, String user, Collection roles) { + Set adminUsers = userTaskInstance.getAdminUsers(); + if (adminUsers.contains(user)) { + return true; + } + + Set userAdminGroups = new HashSet<>(userTaskInstance.getAdminGroups()); + userAdminGroups.retainAll(roles); + if (!userAdminGroups.isEmpty()) { + return true; + } + + if (userTaskInstance.getActualOwner() != null && userTaskInstance.getActualOwner().equals(user)) { + return true; + } + + // there is no user + Set users = new HashSet<>(userTaskInstance.getPotentialUsers()); + users.removeAll(userTaskInstance.getExcludedUsers()); + if (users.contains(user)) { + return true; + } + + Set userPotGroups = new HashSet<>(userTaskInstance.getPotentialGroups()); + userPotGroups.retainAll(roles); + if (!userPotGroups.isEmpty()) { + return true; + } + + return false; + } + @Override public boolean exists(String userTaskInstanceId) { return userTaskInstances.containsKey(userTaskInstanceId); @@ -74,6 +143,7 @@ public UserTaskInstance create(UserTaskInstance userTaskInstance) { userTaskInstances.put(userTaskInstance.getId(), data); return reconnectUserTaskInstance.apply(userTaskInstance); } catch (Exception e) { + LOG.error("during create {}", userTaskInstance.getId(), e); return null; } } @@ -85,6 +155,7 @@ public UserTaskInstance update(UserTaskInstance userTaskInstance) { userTaskInstances.put(userTaskInstance.getId(), data); return userTaskInstance; } catch (Exception e) { + LOG.error("during udpate {}", userTaskInstance.getId(), e); return null; } } @@ -92,8 +163,12 @@ public UserTaskInstance update(UserTaskInstance userTaskInstance) { @Override public UserTaskInstance remove(String userTaskInstanceId) { try { + if (!userTaskInstances.containsKey(userTaskInstanceId)) { + return null; + } return disconnectUserTaskInstance.apply(mapper.readValue(userTaskInstances.remove(userTaskInstanceId), DefaultUserTaskInstance.class)); } catch (Exception e) { + LOG.error("during remove {}", userTaskInstanceId, e); return null; } } diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/KogitoUserTaskEventSupportImpl.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/KogitoUserTaskEventSupportImpl.java index 1a2c3be5d6e..b337d159d4e 100644 --- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/KogitoUserTaskEventSupportImpl.java +++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/KogitoUserTaskEventSupportImpl.java @@ -37,6 +37,7 @@ import org.kie.kogito.usertask.impl.events.UserTaskDeadlineEventImpl; import org.kie.kogito.usertask.impl.events.UserTaskStateEventImpl; import org.kie.kogito.usertask.impl.events.UserTaskVariableEventImpl; +import org.kie.kogito.usertask.lifecycle.UserTaskState; import org.kie.kogito.usertask.model.Attachment; import org.kie.kogito.usertask.model.Comment; @@ -84,7 +85,7 @@ private void fireUserTaskNotification( @Override public void fireOneUserTaskStateChange( UserTaskInstance userTaskInstance, - String oldStatus, String newStatus) { + UserTaskState oldStatus, UserTaskState newStatus) { UserTaskStateEventImpl event = new UserTaskStateEventImpl(userTaskInstance, oldStatus, newStatus, identityProvider.getName()); event.setOldStatus(oldStatus); event.setNewStatus(newStatus); diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/UserTaskServiceImpl.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/UserTaskServiceImpl.java new file mode 100644 index 00000000000..da83176f6d8 --- /dev/null +++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/UserTaskServiceImpl.java @@ -0,0 +1,265 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.usertask.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.kie.kogito.Application; +import org.kie.kogito.auth.IdentityProvider; +import org.kie.kogito.services.uow.UnitOfWorkExecutor; +import org.kie.kogito.usertask.UserTaskConfig; +import org.kie.kogito.usertask.UserTaskInstance; +import org.kie.kogito.usertask.UserTaskService; +import org.kie.kogito.usertask.UserTasks; +import org.kie.kogito.usertask.lifecycle.UserTaskLifeCycle; +import org.kie.kogito.usertask.lifecycle.UserTaskTransition; +import org.kie.kogito.usertask.model.Attachment; +import org.kie.kogito.usertask.model.Comment; +import org.kie.kogito.usertask.view.UserTaskTransitionView; +import org.kie.kogito.usertask.view.UserTaskView; + +public class UserTaskServiceImpl implements UserTaskService { + + private Application application; + + public UserTaskServiceImpl(Application application) { + this.application = application; + } + + @Override + public Optional getUserTaskInstance(String taskId, IdentityProvider identity) { + return application.get(UserTasks.class).instances().findById(taskId).map(this::toUserTaskView); + } + + @Override + public List list(IdentityProvider identity) { + return application.get(UserTasks.class).instances().findByIdentity(identity).stream().map(this::toUserTaskView).toList(); + } + + private UserTaskView toUserTaskView(UserTaskInstance instance) { + UserTaskView view = new UserTaskView(); + view.setId(instance.getId()); + view.setUserTaskId(instance.getUserTaskId()); + view.setStatus(instance.getStatus()); + view.setTaskName(instance.getTaskName()); + view.setTaskDescription(instance.getTaskDescription()); + view.setTaskPriority(instance.getTaskPriority()); + view.setPotentialUsers(instance.getPotentialUsers()); + view.setPotentialGroups(instance.getPotentialGroups()); + view.setExcludedUsers(instance.getExcludedUsers()); + view.setAdminUsers(instance.getAdminUsers()); + view.setAdminGroups(instance.getAdminGroups()); + view.setActualOwner(instance.getActualOwner()); + view.setInputs(instance.getInputs()); + view.setOutputs(instance.getOutputs()); + view.setMetadata(instance.getMetadata()); + return view; + } + + @Override + public Optional transition(String taskId, String transitionId, Map data, IdentityProvider identity) { + return UnitOfWorkExecutor.executeInUnitOfWork(application.unitOfWorkManager(), () -> { + Optional userTaskInstance = application.get(UserTasks.class).instances().findById(taskId); + if (userTaskInstance.isEmpty()) { + return Optional.empty(); + } + UserTaskInstance ut = userTaskInstance.get(); + ut.transition(transitionId, data, identity); + return Optional.of(toUserTaskView(ut)); + }); + } + + @Override + public List allowedTransitions(String taskId, IdentityProvider identity) { + Optional userTaskInstance = application.get(UserTasks.class).instances().findById(taskId); + if (userTaskInstance.isEmpty()) { + return Collections.emptyList(); + } + UserTaskInstance ut = userTaskInstance.get(); + UserTaskLifeCycle userTaskLifeCycle = application.config().get(UserTaskConfig.class).userTaskLifeCycle(); + List transitions = userTaskLifeCycle.allowedTransitions(ut); + return toUserTaskTransitionView(transitions); + } + + private List toUserTaskTransitionView(List transitions) { + List views = new ArrayList<>(); + for (UserTaskTransition transition : transitions) { + UserTaskTransitionView view = new UserTaskTransitionView(); + view.setTransitionId(transition.id()); + view.setSource(transition.source()); + view.setTarget(transition.target()); + views.add(view); + } + + return views; + } + + @Override + public Optional setOutputs(String taskId, Map data, IdentityProvider identity) { + return UnitOfWorkExecutor.executeInUnitOfWork(application.unitOfWorkManager(), () -> { + Optional userTaskInstance = application.get(UserTasks.class).instances().findById(taskId); + if (userTaskInstance.isEmpty()) { + return Optional.empty(); + } + UserTaskInstance ut = userTaskInstance.get(); + data.forEach(ut::setOutput); + return Optional.of(toUserTaskView(ut)); + }); + } + + @Override + public Optional setInputs(String taskId, Map data, IdentityProvider identity) { + return UnitOfWorkExecutor.executeInUnitOfWork(application.unitOfWorkManager(), () -> { + Optional userTaskInstance = application.get(UserTasks.class).instances().findById(taskId); + if (userTaskInstance.isEmpty()) { + return Optional.empty(); + } + UserTaskInstance ut = userTaskInstance.get(); + data.forEach(ut::setInput); + return Optional.of(toUserTaskView(ut)); + }); + } + + @Override + public List getComments(String taskId, IdentityProvider identity) { + Optional userTaskInstance = application.get(UserTasks.class).instances().findById(taskId); + if (userTaskInstance.isEmpty()) { + return Collections.emptyList(); + } + UserTaskInstance ut = userTaskInstance.get(); + return new ArrayList<>(ut.getComments()); + } + + @Override + public Optional getComment(String taskId, String commentId, IdentityProvider identity) { + Optional userTaskInstance = application.get(UserTasks.class).instances().findById(taskId); + if (userTaskInstance.isEmpty()) { + return Optional.empty(); + } + UserTaskInstance ut = userTaskInstance.get(); + return Optional.ofNullable(ut.findCommentById(commentId)); + } + + @Override + public Optional addComment(String taskId, Comment comment, IdentityProvider identity) { + return UnitOfWorkExecutor.executeInUnitOfWork(application.unitOfWorkManager(), () -> { + Optional userTaskInstance = application.get(UserTasks.class).instances().findById(taskId); + if (userTaskInstance.isEmpty()) { + return Optional.empty(); + } + UserTaskInstance ut = userTaskInstance.get(); + Comment wrap = new Comment(null, identity.getName()); + wrap.setContent(comment.getContent()); + return Optional.ofNullable(ut.addComment(wrap)); + }); + } + + @Override + public Optional updateComment(String taskId, Comment comment, IdentityProvider identity) { + return UnitOfWorkExecutor.executeInUnitOfWork(application.unitOfWorkManager(), () -> { + Optional userTaskInstance = application.get(UserTasks.class).instances().findById(taskId); + if (userTaskInstance.isEmpty()) { + return Optional.empty(); + } + UserTaskInstance ut = userTaskInstance.get(); + Comment wrap = new Comment(comment.getId(), identity.getName()); + wrap.setContent(comment.getContent()); + return Optional.ofNullable(ut.updateComment(wrap)); + }); + } + + @Override + public Optional removeComment(String taskId, String commentId, IdentityProvider identity) { + return UnitOfWorkExecutor.executeInUnitOfWork(application.unitOfWorkManager(), () -> { + Optional userTaskInstance = application.get(UserTasks.class).instances().findById(taskId); + if (userTaskInstance.isEmpty()) { + return Optional.empty(); + } + UserTaskInstance ut = userTaskInstance.get(); + return Optional.ofNullable(ut.removeComment(new Comment(commentId, identity.getName()))); + }); + } + + @Override + public List getAttachments(String taskId, IdentityProvider identity) { + Optional userTaskInstance = application.get(UserTasks.class).instances().findById(taskId); + if (userTaskInstance.isEmpty()) { + return Collections.emptyList(); + } + UserTaskInstance ut = userTaskInstance.get(); + return new ArrayList<>(ut.getAttachments()); + } + + @Override + public Optional getAttachment(String taskId, String attachmentId, IdentityProvider identity) { + Optional userTaskInstance = application.get(UserTasks.class).instances().findById(taskId); + if (userTaskInstance.isEmpty()) { + return Optional.empty(); + } + UserTaskInstance ut = userTaskInstance.get(); + return Optional.ofNullable(ut.findAttachmentById(attachmentId)); + } + + @Override + public Optional addAttachment(String taskId, Attachment attachment, IdentityProvider identity) { + return UnitOfWorkExecutor.executeInUnitOfWork(application.unitOfWorkManager(), () -> { + Optional userTaskInstance = application.get(UserTasks.class).instances().findById(taskId); + if (userTaskInstance.isEmpty()) { + return Optional.empty(); + } + UserTaskInstance ut = userTaskInstance.get(); + Attachment wrap = new Attachment(null, identity.getName()); + wrap.setContent(attachment.getContent()); + wrap.setName(attachment.getName()); + return Optional.ofNullable(ut.addAttachment(wrap)); + }); + } + + @Override + public Optional updateAttachment(String taskId, Attachment attachment, IdentityProvider identity) { + return UnitOfWorkExecutor.executeInUnitOfWork(application.unitOfWorkManager(), () -> { + Optional userTaskInstance = application.get(UserTasks.class).instances().findById(taskId); + if (userTaskInstance.isEmpty()) { + return Optional.empty(); + } + UserTaskInstance ut = userTaskInstance.get(); + Attachment wrap = new Attachment(attachment.getId(), identity.getName()); + wrap.setContent(attachment.getContent()); + wrap.setName(attachment.getName()); + return Optional.ofNullable(ut.updateAttachment(attachment)); + }); + } + + @Override + public Optional removeAttachment(String taskId, String attachmentId, IdentityProvider identity) { + return UnitOfWorkExecutor.executeInUnitOfWork(application.unitOfWorkManager(), () -> { + Optional userTaskInstance = application.get(UserTasks.class).instances().findById(taskId); + if (userTaskInstance.isEmpty()) { + return Optional.empty(); + } + UserTaskInstance ut = userTaskInstance.get(); + return Optional.ofNullable(ut.removeAttachment(new Attachment(attachmentId, identity.getName()))); + }); + } + +} diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/events/UserTaskStateEventImpl.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/events/UserTaskStateEventImpl.java index 1bc9005e1f3..e7f734ed089 100644 --- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/events/UserTaskStateEventImpl.java +++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/events/UserTaskStateEventImpl.java @@ -20,34 +20,37 @@ import org.kie.kogito.usertask.UserTaskInstance; import org.kie.kogito.usertask.events.UserTaskStateEvent; +import org.kie.kogito.usertask.lifecycle.UserTaskState; public class UserTaskStateEventImpl extends UserTaskEventImpl implements UserTaskStateEvent { private static final long serialVersionUID = 4556236095420836309L; - private String oldStatus; - private String newStatus; + private UserTaskState oldStatus; + private UserTaskState newStatus; - public UserTaskStateEventImpl(UserTaskInstance userTaskInstance, String oldStatus, String newStatus, String user) { + public UserTaskStateEventImpl(UserTaskInstance userTaskInstance, UserTaskState oldStatus, UserTaskState newStatus, String user) { super(userTaskInstance, user); + this.oldStatus = oldStatus; + this.newStatus = newStatus; } - public void setOldStatus(String oldStatus) { + public void setOldStatus(UserTaskState oldStatus) { this.oldStatus = oldStatus; } - public void setNewStatus(String newStatus) { + public void setNewStatus(UserTaskState newStatus) { this.newStatus = newStatus; } @Override - public String getNewStatus() { + public UserTaskState getNewStatus() { return newStatus; } @Override - public String getOldStatus() { + public UserTaskState getOldStatus() { return oldStatus; } diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/lifecycle/DefaultUserTaskLifeCycle.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/lifecycle/DefaultUserTaskLifeCycle.java index da681ed8f4d..369c7fc6bca 100644 --- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/lifecycle/DefaultUserTaskLifeCycle.java +++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/lifecycle/DefaultUserTaskLifeCycle.java @@ -18,34 +18,53 @@ */ package org.kie.kogito.usertask.impl.lifecycle; +import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; +import org.kie.kogito.auth.IdentityProvider; +import org.kie.kogito.internal.process.workitem.NotAuthorizedException; +import org.kie.kogito.usertask.UserTaskAssignmentStrategy; import org.kie.kogito.usertask.UserTaskInstance; +import org.kie.kogito.usertask.impl.DefaultUserTaskInstance; import org.kie.kogito.usertask.lifecycle.UserTaskLifeCycle; import org.kie.kogito.usertask.lifecycle.UserTaskState; import org.kie.kogito.usertask.lifecycle.UserTaskState.TerminationType; import org.kie.kogito.usertask.lifecycle.UserTaskTransition; +import org.kie.kogito.usertask.lifecycle.UserTaskTransitionException; import org.kie.kogito.usertask.lifecycle.UserTaskTransitionToken; public class DefaultUserTaskLifeCycle implements UserTaskLifeCycle { + public static final String WORKFLOW_ENGINE_USER = "WORKFLOW_ENGINE_USER"; - public static final UserTaskState INACTIVE = UserTaskState.of(null); + public static final String PARAMETER_USER = "USER"; + public static final String PARAMETER_NOTIFY = "NOTIFY"; + + public static final String ACTIVATE = "activate"; + public static final String CLAIM = "claim"; + public static final String RELEASE = "release"; + public static final String COMPLETE = "complete"; + public static final String SKIP = "skip"; + public static final String FAIL = "fail"; + + public static final UserTaskState INACTIVE = UserTaskState.initalized(); public static final UserTaskState ACTIVE = UserTaskState.of("Ready"); public static final UserTaskState RESERVED = UserTaskState.of("Reserved"); public static final UserTaskState COMPLETED = UserTaskState.of("Completed", TerminationType.COMPLETED); public static final UserTaskState ERROR = UserTaskState.of("Error", TerminationType.ERROR); public static final UserTaskState OBSOLETE = UserTaskState.of("Obsolete", TerminationType.OBSOLETE); - private static final UserTaskTransition T_NEW_ACTIVE = new DefaultUserTransition("activate", INACTIVE, ACTIVE, DefaultUserTaskLifeCycle::activate); - private static final UserTaskTransition T_ACTIVE_RESERVED = new DefaultUserTransition("claim", ACTIVE, RESERVED, DefaultUserTaskLifeCycle::claim); - private static final UserTaskTransition T_RESERVED_ACTIVE = new DefaultUserTransition("release", RESERVED, ACTIVE, DefaultUserTaskLifeCycle::release); - private static final UserTaskTransition T_ACTIVE_COMPLETED = new DefaultUserTransition("complete", ACTIVE, COMPLETED, DefaultUserTaskLifeCycle::complete); - private static final UserTaskTransition T_RESERVED_COMPLETED = new DefaultUserTransition("complete", RESERVED, COMPLETED, DefaultUserTaskLifeCycle::complete); - private static final UserTaskTransition T_RESERVED_SKIPPED = new DefaultUserTransition("skip", RESERVED, OBSOLETE, DefaultUserTaskLifeCycle::skip); - private static final UserTaskTransition T_ACTIVE_SKIPPED = new DefaultUserTransition("skip", ACTIVE, OBSOLETE, DefaultUserTaskLifeCycle::complete); - private static final UserTaskTransition T_RESERVED_ERROR = new DefaultUserTransition("fail", RESERVED, ERROR, DefaultUserTaskLifeCycle::fail); + private final UserTaskTransition T_NEW_ACTIVE = new DefaultUserTransition(ACTIVATE, INACTIVE, ACTIVE, this::activate); + private final UserTaskTransition T_ACTIVE_RESERVED = new DefaultUserTransition(CLAIM, ACTIVE, RESERVED, this::claim); + private final UserTaskTransition T_ACTIVE_SKIPPED = new DefaultUserTransition(SKIP, ACTIVE, OBSOLETE, this::skip); + private final UserTaskTransition T_ACTIVE_ERROR = new DefaultUserTransition(FAIL, ACTIVE, ERROR, this::fail); + private final UserTaskTransition T_RESERVED_ACTIVE = new DefaultUserTransition(RELEASE, RESERVED, ACTIVE, this::release); + private final UserTaskTransition T_RESERVED_COMPLETED = new DefaultUserTransition(COMPLETE, RESERVED, COMPLETED, this::complete); + private final UserTaskTransition T_RESERVED_SKIPPED = new DefaultUserTransition(SKIP, RESERVED, OBSOLETE, this::skip); + private final UserTaskTransition T_RESERVED_ERROR = new DefaultUserTransition(FAIL, RESERVED, ERROR, this::fail); private List transitions; @@ -53,58 +72,140 @@ public DefaultUserTaskLifeCycle() { transitions = List.of( T_NEW_ACTIVE, T_ACTIVE_RESERVED, + T_ACTIVE_SKIPPED, + T_ACTIVE_ERROR, T_RESERVED_ACTIVE, - T_ACTIVE_COMPLETED, T_RESERVED_COMPLETED, - T_ACTIVE_SKIPPED, T_RESERVED_SKIPPED, T_RESERVED_ERROR); } @Override - public Optional transition(UserTaskInstance userTaskInstance, UserTaskTransitionToken transition) { - return Optional.empty(); + public List allowedTransitions(UserTaskInstance userTaskInstance) { + return transitions.stream().filter(t -> t.source().equals(userTaskInstance.getStatus())).toList(); } @Override - public UserTaskTransitionToken newTransitionToken(String transitionId, UserTaskInstance userTaskInstance, Map data) { - UserTaskState state = userTaskInstance.getStatus(); - UserTaskTransition transition = transitions.stream().filter(e -> e.source().equals(state) && e.id().equals(transitionId)).findAny() - .orElseThrow(() -> new RuntimeException("Invalid transition " + transitionId + " from " + state)); - return new DefaultUserTaskTransitionToken(transition, data); + public Optional transition(UserTaskInstance userTaskInstance, UserTaskTransitionToken userTaskTransitionToken, IdentityProvider identityProvider) { + checkPermission(userTaskInstance, identityProvider); + UserTaskTransition transition = transitions.stream() + .filter(t -> t.source().equals(userTaskInstance.getStatus()) && t.id().equals(userTaskTransitionToken.transitionId())) + .findFirst() + .orElseThrow(() -> new UserTaskTransitionException("Invalid transition from " + userTaskInstance.getStatus())); + return transition.executor().execute(userTaskInstance, userTaskTransitionToken, identityProvider); } @Override public UserTaskTransitionToken newCompleteTransitionToken(UserTaskInstance userTaskInstance, Map data) { - return newTransitionToken("complete", userTaskInstance, data); + return newTransitionToken(COMPLETE, userTaskInstance.getStatus(), data); } @Override public UserTaskTransitionToken newAbortTransitionToken(UserTaskInstance userTaskInstance, Map data) { - return newTransitionToken("fail", userTaskInstance, data); + return newTransitionToken(FAIL, userTaskInstance.getStatus(), data); } - public static Optional activate(UserTaskInstance userTaskInstance, UserTaskTransitionToken token) { + @Override + public UserTaskTransitionToken newTransitionToken(String transitionId, UserTaskInstance userTaskInstance, Map data) { + return newTransitionToken(transitionId, userTaskInstance.getStatus(), data); + } + + public UserTaskTransitionToken newTransitionToken(String transitionId, UserTaskState state, Map data) { + UserTaskTransition transition = transitions.stream().filter(e -> e.source().equals(state) && e.id().equals(transitionId)).findAny() + .orElseThrow(() -> new RuntimeException("Invalid transition " + transitionId + " from " + state)); + return new DefaultUserTaskTransitionToken(transition.id(), transition.source(), transition.target(), data); + } + + public Optional activate(UserTaskInstance userTaskInstance, UserTaskTransitionToken token, IdentityProvider identityProvider) { + String user = assignStrategy(userTaskInstance, identityProvider); + if (user != null) { + return Optional.of(newTransitionToken(CLAIM, ACTIVE, Map.of(PARAMETER_USER, user))); + } return Optional.empty(); } - public static Optional claim(UserTaskInstance userTaskInstance, UserTaskTransitionToken token) { + public Optional claim(UserTaskInstance userTaskInstance, UserTaskTransitionToken token, IdentityProvider identityProvider) { + if (userTaskInstance instanceof DefaultUserTaskInstance defaultUserTaskInstance) { + if (token.data().containsKey(PARAMETER_USER)) { + defaultUserTaskInstance.setActuaOwner((String) token.data().get(PARAMETER_USER)); + } else { + defaultUserTaskInstance.setActuaOwner(identityProvider.getName()); + } + } return Optional.empty(); } - public static Optional release(UserTaskInstance userTaskInstance, UserTaskTransitionToken token) { + public Optional release(UserTaskInstance userTaskInstance, UserTaskTransitionToken token, IdentityProvider identityProvider) { + if (userTaskInstance instanceof DefaultUserTaskInstance defaultUserTaskInstance) { + defaultUserTaskInstance.setActuaOwner(null); + } return Optional.empty(); } - public static Optional complete(UserTaskInstance userTaskInstance, UserTaskTransitionToken token) { + public Optional complete(UserTaskInstance userTaskInstance, UserTaskTransitionToken token, IdentityProvider identityProvider) { + token.data().forEach(userTaskInstance::setOutput); return Optional.empty(); } - public static Optional skip(UserTaskInstance userTaskInstance, UserTaskTransitionToken token) { + public Optional skip(UserTaskInstance userTaskInstance, UserTaskTransitionToken token, IdentityProvider identityProvider) { + if (token.data().containsKey(PARAMETER_NOTIFY)) { + userTaskInstance.getMetadata().put(PARAMETER_NOTIFY, token.data().get(PARAMETER_NOTIFY)); + } return Optional.empty(); } - public static Optional fail(UserTaskInstance userTaskInstance, UserTaskTransitionToken token) { + public Optional fail(UserTaskInstance userTaskInstance, UserTaskTransitionToken token, IdentityProvider identityProvider) { + if (token.data().containsKey(PARAMETER_NOTIFY)) { + userTaskInstance.getMetadata().put(PARAMETER_NOTIFY, token.data().get(PARAMETER_NOTIFY)); + } return Optional.empty(); } + + private String assignStrategy(UserTaskInstance userTaskInstance, IdentityProvider identityProvider) { + UserTaskAssignmentStrategy assignmentStrategy = userTaskInstance.getUserTask().getAssignmentStrategy(); + return assignmentStrategy.computeAssigment(userTaskInstance, identityProvider).orElse(null); + } + + private void checkPermission(UserTaskInstance userTaskInstance, IdentityProvider identityProvider) { + String user = identityProvider.getName(); + Collection roles = identityProvider.getRoles(); + + if (WORKFLOW_ENGINE_USER.equals(user)) { + return; + } + + // first we check admins + Set adminUsers = userTaskInstance.getAdminUsers(); + if (adminUsers.contains(user)) { + return; + } + + Set userAdminGroups = new HashSet<>(userTaskInstance.getAdminGroups()); + userAdminGroups.retainAll(roles); + if (!userAdminGroups.isEmpty()) { + return; + } + + if (userTaskInstance.getActualOwner() != null && userTaskInstance.getActualOwner().equals(user)) { + return; + } + + if (List.of(INACTIVE, ACTIVE).contains(userTaskInstance.getStatus())) { + // there is no user + Set users = new HashSet<>(userTaskInstance.getPotentialUsers()); + users.removeAll(userTaskInstance.getExcludedUsers()); + if (users.contains(identityProvider.getName())) { + return; + } + + Set userPotGroups = new HashSet<>(userTaskInstance.getPotentialGroups()); + userPotGroups.retainAll(roles); + if (!userPotGroups.isEmpty()) { + return; + } + } + + throw new NotAuthorizedException("user " + user + " with roles " + roles + " not autorized to perform an operation on user task " + userTaskInstance.getId()); + } + } diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/lifecycle/DefaultUserTaskTransitionToken.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/lifecycle/DefaultUserTaskTransitionToken.java index 303d9d8e80a..f8cfd7733fc 100644 --- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/lifecycle/DefaultUserTaskTransitionToken.java +++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/lifecycle/DefaultUserTaskTransitionToken.java @@ -20,21 +20,25 @@ import java.util.Map; -import org.kie.kogito.usertask.lifecycle.UserTaskTransition; +import org.kie.kogito.usertask.lifecycle.UserTaskState; import org.kie.kogito.usertask.lifecycle.UserTaskTransitionToken; public class DefaultUserTaskTransitionToken implements UserTaskTransitionToken { - private UserTaskTransition transition; + private String transition; private Map data; + private UserTaskState source; + private UserTaskState target; - public DefaultUserTaskTransitionToken(UserTaskTransition transition, Map data) { + public DefaultUserTaskTransitionToken(String transition, UserTaskState source, UserTaskState target, Map data) { this.transition = transition; + this.source = source; + this.target = target; this.data = data; } @Override - public UserTaskTransition transition() { + public String transitionId() { return transition; } @@ -43,4 +47,14 @@ public Map data() { return data; } + @Override + public UserTaskState source() { + return source; + } + + @Override + public UserTaskState target() { + return target; + } + } diff --git a/jbpm/process-workitems/src/main/java/org/kie/kogito/process/workitems/impl/DefaultKogitoWorkItemHandler.java b/jbpm/process-workitems/src/main/java/org/kie/kogito/process/workitems/impl/DefaultKogitoWorkItemHandler.java index 993fb8f8ade..21ba12d2d16 100644 --- a/jbpm/process-workitems/src/main/java/org/kie/kogito/process/workitems/impl/DefaultKogitoWorkItemHandler.java +++ b/jbpm/process-workitems/src/main/java/org/kie/kogito/process/workitems/impl/DefaultKogitoWorkItemHandler.java @@ -45,6 +45,11 @@ public class DefaultKogitoWorkItemHandler implements KogitoWorkItemHandler { public static final String TRANSITION_ACTIVATE = "activate"; public static final String TRANSITION_SKIP = "skip"; + public static final WorkItemPhaseState INITIALIZED = WorkItemPhaseState.initialized(); + public static final WorkItemPhaseState COMPLETED = WorkItemPhaseState.of("Completed", WorkItemTerminationType.COMPLETE); + public static final WorkItemPhaseState ABORTED = WorkItemPhaseState.of("Aborted", WorkItemTerminationType.ABORT); + public static final WorkItemPhaseState ACTIVATED = WorkItemPhaseState.of("Activated"); + private static Logger LOG = LoggerFactory.getLogger(DefaultKogitoWorkItemHandler.class); protected Application application; @@ -60,15 +65,10 @@ public DefaultKogitoWorkItemHandler() { } public WorkItemLifeCycle initialize() { - WorkItemPhaseState initialized = WorkItemPhaseState.initialized(); - WorkItemPhaseState completed = WorkItemPhaseState.of("Completed", WorkItemTerminationType.COMPLETE); - WorkItemPhaseState aborted = WorkItemPhaseState.of("Aborted", WorkItemTerminationType.ABORT); - WorkItemPhaseState activated = WorkItemPhaseState.of("Activated"); - - DefaultWorkItemLifeCyclePhase complete = new DefaultWorkItemLifeCyclePhase(TRANSITION_COMPLETE, activated, completed, this::completeWorkItemHandler); - DefaultWorkItemLifeCyclePhase abort = new DefaultWorkItemLifeCyclePhase(TRANSITION_ABORT, activated, aborted, this::abortWorkItemHandler); - DefaultWorkItemLifeCyclePhase active = new DefaultWorkItemLifeCyclePhase(TRANSITION_ACTIVATE, initialized, activated, this::activateWorkItemHandler); - DefaultWorkItemLifeCyclePhase skip = new DefaultWorkItemLifeCyclePhase(TRANSITION_SKIP, activated, completed, this::skipWorkItemHandler); + DefaultWorkItemLifeCyclePhase complete = new DefaultWorkItemLifeCyclePhase(TRANSITION_COMPLETE, ACTIVATED, COMPLETED, this::completeWorkItemHandler); + DefaultWorkItemLifeCyclePhase abort = new DefaultWorkItemLifeCyclePhase(TRANSITION_ABORT, ACTIVATED, ABORTED, this::abortWorkItemHandler); + DefaultWorkItemLifeCyclePhase active = new DefaultWorkItemLifeCyclePhase(TRANSITION_ACTIVATE, INITIALIZED, ACTIVATED, this::activateWorkItemHandler); + DefaultWorkItemLifeCyclePhase skip = new DefaultWorkItemLifeCyclePhase(TRANSITION_SKIP, ACTIVATED, COMPLETED, this::skipWorkItemHandler); return new DefaultWorkItemLifeCycle(active, skip, abort, complete); } @@ -83,17 +83,17 @@ public Application getApplication() { @Override public WorkItemTransition startingTransition(Map data, Policy... policies) { - return workItemLifeCycle.newTransition("activate", null, data, policies); + return workItemLifeCycle.newTransition(TRANSITION_ACTIVATE, null, data, policies); } @Override public WorkItemTransition abortTransition(String phaseStatus, Policy... policies) { - return workItemLifeCycle.newTransition("abort", phaseStatus, emptyMap(), policies); + return workItemLifeCycle.newTransition(TRANSITION_ABORT, phaseStatus, emptyMap(), policies); } @Override public WorkItemTransition completeTransition(String phaseStatus, Map data, Policy... policies) { - return workItemLifeCycle.newTransition("complete", phaseStatus, data, policies); + return workItemLifeCycle.newTransition(TRANSITION_COMPLETE, phaseStatus, data, policies); } @Override @@ -121,11 +121,11 @@ public Optional activateWorkItemHandler(KogitoWorkItemManage return Optional.empty(); } - public Optional abortWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workitem, WorkItemTransition transition) { + public Optional completeWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workitem, WorkItemTransition transition) { return Optional.empty(); } - public Optional completeWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workitem, WorkItemTransition transition) { + public Optional abortWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workitem, WorkItemTransition transition) { return Optional.empty(); } diff --git a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/PublishEventIT.java b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/PublishEventIT.java index 39efe723397..5284672e0a0 100644 --- a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/PublishEventIT.java +++ b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/PublishEventIT.java @@ -149,8 +149,11 @@ public void testCompensationProcess() throws Exception { } - private Optional findUserTaskInstanceEvent(List> events, String status) { - return events.stream().filter(UserTaskInstanceStateDataEvent.class::isInstance).map(e -> (UserTaskInstanceStateDataEvent) e).filter(e -> status.equals(e.getData().getState())).findAny(); + private Optional findUserTaskInstanceEvent(List> events, String taskName, String status) { + return events.stream().filter(UserTaskInstanceStateDataEvent.class::isInstance) + .map(e -> (UserTaskInstanceStateDataEvent) e) + .filter(e -> status.equals(e.getData().getState()) && e.getData().getUserTaskName().equals(taskName)) + .findAny(); } private Optional> findProcessInstanceEvent(List> events, int state) { @@ -205,7 +208,7 @@ public void onUserTaskState(UserTaskStateEvent event) { List left = findNodeInstanceEvents(events, 2); assertThat(left).hasSize(1).extractingResultOf("getNodeType").containsOnly("StartNode"); - Optional userFirstTask = findUserTaskInstanceEvent(events, "Ready"); + Optional userFirstTask = findUserTaskInstanceEvent(events, "FirstTask", "Ready"); assertThat(userFirstTask).isPresent(); assertUserTaskInstanceEvent(userFirstTask.get(), "FirstTask", null, "1", "Ready", "UserTasksProcess", "First Task"); @@ -226,11 +229,12 @@ public void onUserTaskState(UserTaskStateEvent event) { left = findNodeInstanceEvents(events, 1); assertThat(left).hasSize(1).extractingResultOf("getNodeType").containsOnly("HumanTaskNode"); - Optional firstUserTaskInstance = findUserTaskInstanceEvent(events, "Ready"); - Optional secondUserTaskInstance = findUserTaskInstanceEvent(events, "Completed"); + Optional firstUserTaskInstance = findUserTaskInstanceEvent(events, "SecondTask", "Ready"); + // we completed through the work item handler so the user task is really obsolete + Optional secondUserTaskInstance = findUserTaskInstanceEvent(events, "FirstTask", "Obsolete"); assertUserTaskInstanceEvent(firstUserTaskInstance.get(), "SecondTask", null, "1", "Ready", "UserTasksProcess", "Second Task"); - assertUserTaskInstanceEvent(secondUserTaskInstance.get(), "FirstTask", null, "1", "Completed", "UserTasksProcess", "First Task"); + assertUserTaskInstanceEvent(secondUserTaskInstance.get(), "FirstTask", null, "1", "Obsolete", "UserTasksProcess", "First Task"); workItems = processInstance.workItems(securityPolicy); assertThat(workItems).hasSize(1); @@ -252,7 +256,7 @@ public void onUserTaskState(UserTaskStateEvent event) { left = findNodeInstanceEvents(events, 2); assertThat(left).hasSize(2).extractingResultOf("getNodeType").containsOnly("HumanTaskNode", "EndNode"); - assertUserTaskInstanceEvent(events.get(0), "SecondTask", null, "1", "Completed", "UserTasksProcess", "Second Task"); + assertUserTaskInstanceEvent(events.get(0), "SecondTask", null, "1", "Obsolete", "UserTasksProcess", "Second Task"); } @Test @@ -285,7 +289,7 @@ public void testBasicUserTaskProcessAbort() throws Exception { List triggered = findNodeInstanceEvents(events, 1); assertThat(triggered).hasSize(2).extractingResultOf("getNodeName").containsOnly("StartProcess", "First Task"); - Optional event = findUserTaskInstanceEvent(events, "Ready"); + Optional event = findUserTaskInstanceEvent(events, "FirstTask", "Ready"); assertThat(event).isPresent(); assertUserTaskInstanceEvent(event.get(), "FirstTask", null, "1", "Ready", "UserTasksProcess", "First Task"); @@ -343,7 +347,7 @@ public void testBasicUserTaskProcessWithSecurityRoles() throws Exception { List left = findNodeInstanceEvents(events, 2); assertThat(left).hasSize(1).extractingResultOf("getNodeType").containsOnly("StartNode"); - Optional userTask = findUserTaskInstanceEvent(events, "Ready"); + Optional userTask = findUserTaskInstanceEvent(events, "FirstTask", "Ready"); assertThat(userTask).isPresent(); assertUserTaskInstanceEvent(userTask.get(), "FirstTask", null, "1", "Ready", "UserTasksProcess", "First Task"); } diff --git a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/UserTaskIT.java b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/UserTaskIT.java index dc15d15ae9f..7a0e493fa4f 100644 --- a/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/UserTaskIT.java +++ b/kogito-codegen-modules/kogito-codegen-processes-integration-tests/src/test/java/org/kie/kogito/codegen/tests/UserTaskIT.java @@ -26,7 +26,6 @@ import java.util.Map; import java.util.Optional; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.kie.kogito.Application; import org.kie.kogito.Model; @@ -38,31 +37,32 @@ import org.kie.kogito.internal.process.event.DefaultKogitoProcessEventListener; import org.kie.kogito.internal.process.event.ProcessWorkItemTransitionEvent; import org.kie.kogito.internal.process.runtime.KogitoProcessInstance; -import org.kie.kogito.internal.process.workitem.KogitoWorkItemHandler; import org.kie.kogito.internal.process.workitem.NotAuthorizedException; import org.kie.kogito.internal.process.workitem.Policy; -import org.kie.kogito.internal.process.workitem.WorkItemNotFoundException; -import org.kie.kogito.internal.process.workitem.WorkItemTransition; +import org.kie.kogito.jbpm.usertask.handler.UserTaskKogitoWorkItemHandler; +import org.kie.kogito.jbpm.usertask.handler.UserTaskKogitoWorkItemHandlerProcessListener; import org.kie.kogito.process.Process; import org.kie.kogito.process.ProcessConfig; import org.kie.kogito.process.ProcessInstance; import org.kie.kogito.process.Processes; import org.kie.kogito.process.VariableViolationException; import org.kie.kogito.process.WorkItem; +import org.kie.kogito.usertask.UserTask; +import org.kie.kogito.usertask.UserTaskConfig; +import org.kie.kogito.usertask.UserTaskInstance; +import org.kie.kogito.usertask.UserTasks; +import org.kie.kogito.usertask.impl.lifecycle.DefaultUserTaskLifeCycle; import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; -import static org.kie.kogito.jbpm.usertask.handler.UserTaskKogitoWorkItemHandler.ACTIVATED; -import static org.kie.kogito.jbpm.usertask.handler.UserTaskKogitoWorkItemHandler.RESERVED; -import static org.kie.kogito.jbpm.usertask.handler.UserTaskKogitoWorkItemHandler.TRANSITION_ACTIVATED_CLAIM; -import static org.kie.kogito.jbpm.usertask.handler.UserTaskKogitoWorkItemHandler.TRANSITION_RESERVED_COMPLETE; -import static org.kie.kogito.jbpm.usertask.handler.UserTaskKogitoWorkItemHandler.TRANSITION_RESERVED_RELEASE; - -@Disabled +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.kie.kogito.usertask.impl.lifecycle.DefaultUserTaskLifeCycle.CLAIM; +import static org.kie.kogito.usertask.impl.lifecycle.DefaultUserTaskLifeCycle.COMPLETE; +import static org.kie.kogito.usertask.impl.lifecycle.DefaultUserTaskLifeCycle.RELEASE; + public class UserTaskIT extends AbstractCodegenIT { private Policy securityPolicy = SecurityPolicy.of("john", emptyList()); @@ -86,6 +86,9 @@ public void afterWorkItemTransition(ProcessWorkItemTransitionEvent event) { } }); + // we wired user tasks and processes + app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class))); + Process p = app.get(Processes.class).processById("UserTasksProcess"); Model m = p.createModel(); @@ -100,28 +103,55 @@ public void afterWorkItemTransition(ProcessWorkItemTransitionEvent event) { List workItems = processInstance.workItems(securityPolicy); assertThat(workItems).hasSize(1); assertThat(workItems.get(0).getName()).isEqualTo("FirstTask"); - WorkItem wi = workItems.get(0); + WorkItem wi_1 = workItems.get(0); - KogitoWorkItemHandler handler = getWorkItemHandler(p, wi); - WorkItemTransition transition = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy); - processInstance.transitionWorkItem(workItems.get(0).getId(), transition); + UserTasks userTasks = app.get(UserTasks.class); + + UserTask userTask_1 = userTasks.userTaskById(getUserTaskId(wi_1.getExternalReferenceId())); + UserTaskInstance userTaskInstance_1 = userTask_1.instances().findById(getUserTaskInstanceId(wi_1.getExternalReferenceId())).get(); + assertThat(userTaskInstance_1).isNotNull(); + + List userTaskList = userTasks.instances().findByIdentity(IdentityProviders.of("mary")); + assertThat(userTaskList).hasSize(1); + + userTaskList = userTask_1.instances().findByIdentity(IdentityProviders.of("invalid")); + assertThat(userTaskList).hasSize(0); + + userTaskList = userTask_1.instances().findByIdentity(IdentityProviders.of("john")); + assertThat(userTaskList).hasSize(1); + + userTaskInstance_1 = userTaskList.get(0); + userTaskInstance_1.transition(CLAIM, emptyMap(), IdentityProviders.of("john")); + userTaskInstance_1.transition(COMPLETE, emptyMap(), IdentityProviders.of("john")); - workItems = processInstance.workItems(securityPolicy); - assertThat(workItems).hasSize(1); - wi = workItems.get(0); - assertThat(wi.getName()).isEqualTo("FirstTask"); - transition = handler.newTransition(TRANSITION_RESERVED_COMPLETE.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy); - processInstance.transitionWorkItem(workItems.get(0).getId(), transition); assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE); workItems = processInstance.workItems(securityPolicy); assertThat(workItems).hasSize(1); assertThat(workItems.get(0).getName()).isEqualTo("SecondTask"); + WorkItem wi_2 = workItems.get(0); + + UserTask userTask_2 = userTasks.userTaskById(getUserTaskId(wi_2.getExternalReferenceId())); + UserTaskInstance userTaskInstance_2 = userTask_2.instances().findById(getUserTaskInstanceId(wi_2.getExternalReferenceId())).get(); + assertThat(userTaskInstance_2).isNotNull(); + + userTaskList = userTasks.instances().findByIdentity(IdentityProviders.of("john")); + assertThat(userTaskList).hasSize(1); + + userTaskInstance_1 = userTaskList.get(0); + userTaskInstance_2.transition(COMPLETE, emptyMap(), IdentityProviders.of("john")); - processInstance.completeWorkItem(workItems.get(0).getId(), null, securityPolicy); assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_COMPLETED); - assertThat(workItemTransitionEvents).hasSize(12); + assertThat(workItemTransitionEvents).hasSize(8); + } + + private String getUserTaskInstanceId(String externalReference) { + return externalReference.split(":")[1]; + } + + private String getUserTaskId(String externalReference) { + return externalReference.split(":")[0]; } @Test @@ -129,7 +159,7 @@ public void testBasicUserTaskProcessPhases() throws Exception { Application app = generateCodeProcessesOnly("usertask/UserTasksProcess.bpmn2"); assertThat(app).isNotNull(); - + app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class))); Process p = app.get(Processes.class).processById("UserTasksProcess"); Model m = p.createModel(); @@ -146,16 +176,12 @@ public void testBasicUserTaskProcessPhases() throws Exception { WorkItem wi = workItems.get(0); assertThat(wi.getName()).isEqualTo("FirstTask"); - KogitoWorkItemHandler handler = getWorkItemHandler(p, wi); - WorkItemTransition transition = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy); - processInstance.transitionWorkItem(workItems.get(0).getId(), transition); - - workItems = processInstance.workItems(securityPolicy); - assertThat(workItems).hasSize(1); - wi = workItems.get(0); - assertThat(wi.getName()).isEqualTo("FirstTask"); - transition = handler.newTransition(TRANSITION_RESERVED_COMPLETE.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy); - processInstance.transitionWorkItem(workItems.get(0).getId(), transition); + UserTasks userTasks = app.get(UserTasks.class); + List userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john")); + assertThat(userTaskInstances).isNotNull().hasSize(1); + UserTaskInstance ut_1 = userTaskInstances.get(0); + ut_1.transition(CLAIM, emptyMap(), IdentityProviders.of("john")); + ut_1.transition(COMPLETE, emptyMap(), IdentityProviders.of("john")); assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE); @@ -164,9 +190,11 @@ public void testBasicUserTaskProcessPhases() throws Exception { wi = workItems.get(0); assertThat(wi.getName()).isEqualTo("SecondTask"); - handler = getWorkItemHandler(p, wi); - transition = handler.completeTransition(workItems.get(0).getPhaseStatus(), parameters, securityPolicy); - processInstance.transitionWorkItem(workItems.get(0).getId(), transition); + userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john")); + assertThat(userTaskInstances).isNotNull().hasSize(1); + UserTaskInstance ut_2 = userTaskInstances.get(0); + ut_2.transition(COMPLETE, emptyMap(), IdentityProviders.of("john")); + assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_COMPLETED); } @@ -175,7 +203,7 @@ public void testBasicUserTaskProcessClaimAndCompletePhases() throws Exception { Application app = generateCodeProcessesOnly("usertask/UserTasksProcess.bpmn2"); assertThat(app).isNotNull(); - + app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class))); Process p = app.get(Processes.class).processById("UserTasksProcess"); Model m = p.createModel(); @@ -193,32 +221,20 @@ public void testBasicUserTaskProcessClaimAndCompletePhases() throws Exception { assertThat(wi.getName()).isEqualTo("FirstTask"); assertThat(wi.getResults()).isEmpty(); - KogitoWorkItemHandler handler = getWorkItemHandler(p, wi); - WorkItemTransition transition = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), Map.of("ACTUAL_OWNER", "john", "test", "value"), securityPolicy); - processInstance.transitionWorkItem(workItems.get(0).getId(), transition); - + UserTasks userTasks = app.get(UserTasks.class); + List userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john")); + assertThat(userTaskInstances).isNotNull().hasSize(1); + UserTaskInstance ut_1 = userTaskInstances.get(0); + ut_1.transition(CLAIM, emptyMap(), IdentityProviders.of("john")); assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE); - workItems = processInstance.workItems(securityPolicy); - assertThat(workItems).hasSize(1); - wi = workItems.get(0); - assertThat(wi.getName()).isEqualTo("FirstTask"); - - assertThat(wi.getResults()).hasSize(1) - .containsEntry("test", "value"); - - handler = getWorkItemHandler(p, wi); - transition = handler.newTransition(TRANSITION_RESERVED_COMPLETE.id(), wi.getPhaseStatus(), emptyMap(), securityPolicy); - processInstance.transitionWorkItem(workItems.get(0).getId(), transition); - + ut_1.transition(COMPLETE, Map.of("test", "value"), IdentityProviders.of("john")); assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE); - workItems = processInstance.workItems(securityPolicy); - assertThat(workItems).hasSize(1); - wi = workItems.get(0); - assertThat(wi.getName()).isEqualTo("SecondTask"); - assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName()); - assertThat(wi.getResults()).isEmpty(); + userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john")); + assertThat(userTaskInstances).isNotNull().hasSize(1); + UserTaskInstance ut_2 = userTaskInstances.get(0); + assertThat(ut_2.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.RESERVED); processInstance.abort(); assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ABORTED); @@ -229,7 +245,7 @@ public void testBasicUserTaskProcessReleaseAndCompletePhases() throws Exception Application app = generateCodeProcessesOnly("usertask/UserTasksProcess.bpmn2"); assertThat(app).isNotNull(); - + app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class))); Process p = app.get(Processes.class).processById("UserTasksProcess"); Model m = p.createModel(); @@ -245,43 +261,26 @@ public void testBasicUserTaskProcessReleaseAndCompletePhases() throws Exception assertThat(workItems).hasSize(1); WorkItem wi = workItems.get(0); assertThat(wi.getName()).isEqualTo("FirstTask"); - assertThat(wi.getPhaseStatus()).isEqualTo(ACTIVATED.getName()); + assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName()); assertThat(wi.getResults()).isEmpty(); - KogitoWorkItemHandler handler = getWorkItemHandler(p, wi); - assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE); - - WorkItemTransition claim = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy); - processInstance.transitionWorkItem(wi.getId(), claim); + UserTasks userTasks = app.get(UserTasks.class); + List userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john")); + assertThat(userTaskInstances).isNotNull().hasSize(1); + UserTaskInstance ut_1 = userTaskInstances.get(0); + ut_1.transition(CLAIM, emptyMap(), IdentityProviders.of("john")); + assertThat(ut_1.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.RESERVED); + assertThat(ut_1.getActualOwner()).isEqualTo("john"); - workItems = processInstance.workItems(securityPolicy); - assertThat(workItems).hasSize(1); - wi = workItems.get(0); - assertThat(wi.getName()).isEqualTo("FirstTask"); - assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName()); - assertThat(wi.getResults()).isEmpty(); + ut_1.transition(RELEASE, emptyMap(), IdentityProviders.of("john")); + assertThat(ut_1.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.ACTIVE); + assertThat(ut_1.getActualOwner()).isNull(); - WorkItemTransition release = handler.newTransition(TRANSITION_RESERVED_RELEASE.id(), wi.getPhaseStatus(), emptyMap(), securityPolicy); - processInstance.transitionWorkItem(wi.getId(), release); + ut_1.transition(DefaultUserTaskLifeCycle.CLAIM, emptyMap(), IdentityProviders.of("john")); + assertThat(ut_1.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.RESERVED); + assertThat(ut_1.getActualOwner()).isEqualTo("john"); - workItems = processInstance.workItems(securityPolicy); - assertThat(workItems).hasSize(1); - wi = workItems.get(0); - assertThat(wi.getName()).isEqualTo("FirstTask"); - assertThat(wi.getPhaseStatus()).isEqualTo(ACTIVATED.getName()); - assertThat(wi.getResults()).isEmpty(); - - claim = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy); - processInstance.transitionWorkItem(wi.getId(), claim); - - workItems = processInstance.workItems(securityPolicy); - assertThat(workItems).hasSize(1); - wi = workItems.get(0); - assertThat(wi.getName()).isEqualTo("FirstTask"); - assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName()); - assertThat(wi.getResults()).isEmpty(); - WorkItemTransition transition = handler.completeTransition(wi.getPhaseStatus(), emptyMap(), securityPolicy); - processInstance.transitionWorkItem(wi.getId(), transition); + ut_1.transition(DefaultUserTaskLifeCycle.COMPLETE, emptyMap(), IdentityProviders.of("john")); assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE); workItems = processInstance.workItems(securityPolicy); @@ -289,11 +288,14 @@ public void testBasicUserTaskProcessReleaseAndCompletePhases() throws Exception wi = workItems.get(0); assertThat(wi.getName()).isEqualTo("SecondTask"); - assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName()); - assertThat(wi.getResults()).isEmpty(); + userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john")); + assertThat(userTaskInstances).isNotNull().hasSize(1); + UserTaskInstance ut_2 = userTaskInstances.get(0); + assertThat(ut_2.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.RESERVED); processInstance.abort(); assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ABORTED); + assertThat(userTasks.instances().findByIdentity(IdentityProviders.of("john"))).hasSize(0); } @Test @@ -301,6 +303,7 @@ public void testBasicUserTaskProcessClaimAndCompletePhasesWithIdentity() throws Application app = generateCodeProcessesOnly("usertask/UserTasksProcess.bpmn2"); assertThat(app).isNotNull(); + app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class))); final List workItemTransitionEvents = new ArrayList<>(); app.config().get(ProcessConfig.class).processEventListeners().listeners().add(new DefaultKogitoProcessEventListener() { @@ -330,37 +333,30 @@ public void afterWorkItemTransition(ProcessWorkItemTransitionEvent event) { assertThat(workItems).hasSize(1); WorkItem wi = workItems.get(0); assertThat(wi.getName()).isEqualTo("FirstTask"); - assertThat(wi.getPhaseStatus()).isEqualTo(ACTIVATED.getName()); + assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName()); assertThat(wi.getResults()).isEmpty(); - KogitoWorkItemHandler handler = getWorkItemHandler(p, wi); - WorkItemTransition transition = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), Map.of("ACTUAL_OWNER", "john", "test", "value"), securityPolicy); - processInstance.transitionWorkItem(workItems.get(0).getId(), transition); - - workItems = processInstance.workItems(securityPolicy); - assertThat(workItems).hasSize(1); - wi = workItems.get(0); - assertThat(wi.getName()).isEqualTo("FirstTask"); - assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName()); - assertThat(wi.getResults()).hasSize(1) - .containsEntry("test", "value"); - - transition = handler.completeTransition(wi.getPhaseStatus(), emptyMap(), securityPolicy); - processInstance.transitionWorkItem(workItems.get(0).getId(), transition); + UserTasks userTasks = app.get(UserTasks.class); + List userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john")); + assertThat(userTaskInstances).isNotNull().hasSize(1); + UserTaskInstance ut_1 = userTaskInstances.get(0); + assertThat(ut_1.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.ACTIVE); + ut_1.transition(CLAIM, emptyMap(), IdentityProviders.of("john")); + assertThat(ut_1.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.RESERVED); + ut_1.transition(COMPLETE, emptyMap(), IdentityProviders.of("john")); + assertThat(ut_1.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.COMPLETED); assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE); - workItems = processInstance.workItems(securityPolicy); - assertThat(workItems).hasSize(1); - wi = workItems.get(0); - assertThat(wi.getName()).isEqualTo("SecondTask"); - assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName()); - assertThat(wi.getResults()).isEmpty(); + userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john")); + assertThat(userTaskInstances).isNotNull().hasSize(1); + UserTaskInstance ut_2 = userTaskInstances.get(0); + assertThat(ut_2.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.RESERVED); processInstance.abort(); assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ABORTED); - assertThat(workItemTransitionEvents).hasSize(12); + assertThat(workItemTransitionEvents).hasSize(8); } @Test @@ -368,7 +364,7 @@ public void testBasicUserTaskProcessClaimAndCompleteWrongUser() throws Exception Application app = generateCodeProcessesOnly("usertask/UserTasksProcess.bpmn2"); assertThat(app).isNotNull(); - + app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class))); Process p = app.get(Processes.class).processById("UserTasksProcess"); Model m = p.createModel(); @@ -384,7 +380,7 @@ public void testBasicUserTaskProcessClaimAndCompleteWrongUser() throws Exception assertThat(workItems).hasSize(1); WorkItem wi = workItems.get(0); assertThat(wi.getName()).isEqualTo("FirstTask"); - assertThat(wi.getPhaseStatus()).isEqualTo(ACTIVATED.getName()); + assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName()); assertThat(wi.getResults()).isEmpty(); final String wiId = wi.getId(); @@ -394,11 +390,13 @@ public void testBasicUserTaskProcessClaimAndCompleteWrongUser() throws Exception List securedWorkItems = processInstance.workItems(SecurityPolicy.of(identity)); assertThat(securedWorkItems).isEmpty(); - assertThatExceptionOfType(WorkItemNotFoundException.class).isThrownBy(() -> processInstance.workItem(wiId, SecurityPolicy.of(identity))); + assertThatExceptionOfType(NotAuthorizedException.class).isThrownBy(() -> processInstance.workItem(wiId, SecurityPolicy.of(identity))); - KogitoWorkItemHandler handler = getWorkItemHandler(p, wi); - WorkItemTransition claimKelly = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "kelly"), SecurityPolicy.of(identity)); - assertThatExceptionOfType(NotAuthorizedException.class).isThrownBy(() -> processInstance.transitionWorkItem(wiId, claimKelly)); + UserTasks userTasks = app.get(UserTasks.class); + List userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john")); + assertThat(userTaskInstances).isNotNull().hasSize(1); + UserTaskInstance utInvalid = userTaskInstances.get(0); + assertThatExceptionOfType(NotAuthorizedException.class).isThrownBy(() -> utInvalid.transition(CLAIM, emptyMap(), IdentityProviders.of("invalid"))); assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE); @@ -406,32 +404,32 @@ public void testBasicUserTaskProcessClaimAndCompleteWrongUser() throws Exception assertThat(workItems).hasSize(1); wi = workItems.get(0); assertThat(wi.getName()).isEqualTo("FirstTask"); - assertThat(wi.getPhaseStatus()).isEqualTo(ACTIVATED.getName()); + assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName()); assertThat(wi.getResults()).isEmpty(); assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE); - WorkItemTransition claimJohn = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy); - processInstance.transitionWorkItem(wiId, claimJohn); - - workItems = processInstance.workItems(securityPolicy); - assertThat(workItems).hasSize(1); - wi = workItems.get(0); - assertThat(wi.getName()).isEqualTo("FirstTask"); - assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName()); - assertThat(wi.getResults()).isEmpty(); - WorkItemTransition completeJohn = handler.newTransition(TRANSITION_RESERVED_COMPLETE.id(), wi.getPhaseStatus(), emptyMap(), securityPolicy); - processInstance.transitionWorkItem(wiId, completeJohn); + userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john")); + assertThat(userTaskInstances).isNotNull().hasSize(1); + UserTaskInstance ut_1 = userTaskInstances.get(0); + ut_1.transition(CLAIM, emptyMap(), IdentityProviders.of("john")); + ut_1.transition(COMPLETE, emptyMap(), IdentityProviders.of("john")); workItems = processInstance.workItems(securityPolicy); assertThat(workItems).hasSize(1); wi = workItems.get(0); assertThat(wi.getName()).isEqualTo("SecondTask"); - assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName()); + assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName()); assertThat(wi.getResults()).isEmpty(); + userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john")); + assertThat(userTaskInstances).isNotNull().hasSize(1); + UserTaskInstance ut_2 = userTaskInstances.get(0); + assertThat(ut_2.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.RESERVED); + processInstance.abort(); assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ABORTED); + assertThat(userTasks.instances().findByIdentity(IdentityProviders.of("john"))).hasSize(0); } @Test @@ -439,7 +437,7 @@ public void testApprovalWithExcludedOwnerViaPhases() throws Exception { Application app = generateCodeProcessesOnly("usertask/approval.bpmn2"); assertThat(app).isNotNull(); - + app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class))); Process p = app.get(Processes.class).processById("approvals"); Model m = p.createModel(); @@ -458,10 +456,12 @@ public void testApprovalWithExcludedOwnerViaPhases() throws Exception { List workItems = processInstance.workItems(policy); assertThat(workItems).hasSize(1); - WorkItem wi = workItems.get(0); - KogitoWorkItemHandler handler = getWorkItemHandler(p, wi); - WorkItemTransition transition = handler.newTransition(TRANSITION_RESERVED_COMPLETE.id(), wi.getPhaseStatus(), singletonMap("ActorId", "manager"), policy); - processInstance.transitionWorkItem(workItems.get(0).getId(), transition); + UserTasks userTasks = app.get(UserTasks.class); + List userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("manager")); + assertThat(userTaskInstances).isNotNull().hasSize(1); + UserTaskInstance ut_1 = userTaskInstances.get(0); + ut_1.transition(COMPLETE, emptyMap(), IdentityProviders.of("manager")); + // actual owner of the first task is excluded owner on the second task so won't find it workItems = processInstance.workItems(policy); assertThat(workItems).isEmpty(); @@ -471,15 +471,12 @@ public void testApprovalWithExcludedOwnerViaPhases() throws Exception { workItems = processInstance.workItems(policy); assertThat(workItems).hasSize(1); - wi = workItems.get(0); - transition = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), policy); - processInstance.transitionWorkItem(workItems.get(0).getId(), transition); - workItems = processInstance.workItems(policy); - assertThat(workItems).hasSize(1); - wi = workItems.get(0); - transition = handler.newTransition(TRANSITION_RESERVED_COMPLETE.id(), wi.getPhaseStatus(), emptyMap(), policy); - processInstance.transitionWorkItem(workItems.get(0).getId(), transition); + userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("admin", singletonList("managers"))); + assertThat(userTaskInstances).isNotNull().hasSize(1); + UserTaskInstance ut_2 = userTaskInstances.get(0); + ut_2.transition(CLAIM, emptyMap(), IdentityProviders.of("admin", singletonList("managers"))); + ut_2.transition(COMPLETE, emptyMap(), IdentityProviders.of("admin", singletonList("managers"))); assertThat(processInstance.status()).isEqualTo(KogitoProcessInstance.STATE_COMPLETED); } @@ -489,7 +486,7 @@ public void testApprovalWithExcludedOwner() throws Exception { Application app = generateCodeProcessesOnly("usertask/approval.bpmn2"); assertThat(app).isNotNull(); - + app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class))); Process p = app.get(Processes.class).processById("approvals"); Model m = p.createModel(); @@ -519,13 +516,13 @@ public void testApprovalWithExcludedOwner() throws Exception { assertThat(workItems).hasSize(1); assertThat(workItems).hasSize(1); - WorkItem wi = workItems.get(0); - - KogitoWorkItemHandler handler = getWorkItemHandler(p, wi); - WorkItemTransition transition = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), policy); - processInstance.transitionWorkItem(workItems.get(0).getId(), transition); - processInstance.completeWorkItem(workItems.get(0).getId(), null, policy); + UserTasks userTasks = app.get(UserTasks.class); + List userTaskInstances = userTasks.instances().findByIdentity(identity); + assertThat(userTaskInstances).isNotNull().hasSize(1); + UserTaskInstance ut_1 = userTaskInstances.get(0); + ut_1.transition(CLAIM, emptyMap(), identity); + ut_1.transition(COMPLETE, emptyMap(), identity); assertThat(processInstance.status()).isEqualTo(KogitoProcessInstance.STATE_COMPLETED); } @@ -535,7 +532,7 @@ public void testBasicUserTaskProcessCancelAndTriggerNode() throws Exception { Application app = generateCodeProcessesOnly("usertask/UserTasksProcess.bpmn2"); assertThat(app).isNotNull(); - + app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class))); Process p = app.get(Processes.class).processById("UserTasksProcess"); Model m = p.createModel(); @@ -547,30 +544,20 @@ public void testBasicUserTaskProcessCancelAndTriggerNode() throws Exception { assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE); - List workItems = processInstance.workItems(securityPolicy); - assertThat(workItems).hasSize(1); - WorkItem wi = workItems.get(0); - assertThat(wi.getName()).isEqualTo("FirstTask"); - assertThat(wi.getPhaseStatus()).isEqualTo(ACTIVATED.getName()); - - KogitoWorkItemHandler handler = getWorkItemHandler(p, wi); - WorkItemTransition transition = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy); - processInstance.transitionWorkItem(workItems.get(0).getId(), transition); - - workItems = processInstance.workItems(securityPolicy); - assertThat(workItems).hasSize(1); - wi = workItems.get(0); - assertThat(wi.getName()).isEqualTo("FirstTask"); - transition = handler.newTransition(TRANSITION_RESERVED_COMPLETE.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy); - processInstance.transitionWorkItem(workItems.get(0).getId(), transition); + UserTasks userTasks = app.get(UserTasks.class); + List userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john")); + assertThat(userTaskInstances).isNotNull().hasSize(1); + UserTaskInstance ut_1 = userTaskInstances.get(0); + ut_1.transition(CLAIM, emptyMap(), IdentityProviders.of("john")); + ut_1.transition(COMPLETE, emptyMap(), IdentityProviders.of("john")); assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE); - workItems = processInstance.workItems(securityPolicy); + List workItems = processInstance.workItems(securityPolicy); assertThat(workItems).hasSize(1); - wi = workItems.get(0); + WorkItem wi = workItems.get(0); assertThat(wi.getName()).isEqualTo("SecondTask"); - assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName()); + assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName()); String firstSecondTaskNodeInstanceId = wi.getNodeInstanceId(); @@ -582,11 +569,14 @@ public void testBasicUserTaskProcessCancelAndTriggerNode() throws Exception { assertThat(workItems).hasSize(1); wi = workItems.get(0); assertThat(wi.getName()).isEqualTo("SecondTask"); - assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName()); + assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName()); // since it was triggered again it must have different node instance id assertThat(wi.getNodeInstanceId()).isNotEqualTo(firstSecondTaskNodeInstanceId); - transition = handler.completeTransition(workItems.get(0).getPhaseStatus(), parameters, securityPolicy); - processInstance.transitionWorkItem(workItems.get(0).getId(), transition); + + userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john")); + assertThat(userTaskInstances).isNotNull().hasSize(1); + UserTaskInstance ut_2 = userTaskInstances.get(0); + ut_2.transition(COMPLETE, emptyMap(), IdentityProviders.of("john")); assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_COMPLETED); } @@ -596,7 +586,7 @@ public void testBasicUserTaskProcessCancelAndRetriggerNode() throws Exception { Application app = generateCodeProcessesOnly("usertask/UserTasksProcess.bpmn2"); assertThat(app).isNotNull(); - + app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class))); Process p = app.get(Processes.class).processById("UserTasksProcess"); Model m = p.createModel(); @@ -613,18 +603,14 @@ public void testBasicUserTaskProcessCancelAndRetriggerNode() throws Exception { WorkItem wi = workItems.get(0); assertThat(wi.getName()).isEqualTo("FirstTask"); - assertThat(wi.getPhaseStatus()).isEqualTo(ACTIVATED.getName()); + assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName()); - KogitoWorkItemHandler handler = getWorkItemHandler(p, wi); - WorkItemTransition transition = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy); - processInstance.transitionWorkItem(workItems.get(0).getId(), transition); - - workItems = processInstance.workItems(securityPolicy); - assertThat(workItems).hasSize(1); - wi = workItems.get(0); - assertThat(wi.getName()).isEqualTo("FirstTask"); - transition = handler.newTransition(TRANSITION_RESERVED_COMPLETE.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy); - processInstance.transitionWorkItem(workItems.get(0).getId(), transition); + UserTasks userTasks = app.get(UserTasks.class); + List userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john")); + assertThat(userTaskInstances).isNotNull().hasSize(1); + UserTaskInstance ut_1 = userTaskInstances.get(0); + ut_1.transition(CLAIM, emptyMap(), IdentityProviders.of("john")); + ut_1.transition(COMPLETE, emptyMap(), IdentityProviders.of("john")); assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE); @@ -632,8 +618,7 @@ public void testBasicUserTaskProcessCancelAndRetriggerNode() throws Exception { assertThat(workItems).hasSize(1); wi = workItems.get(0); assertThat(wi.getName()).isEqualTo("SecondTask"); - - assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName()); + assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName()); String firstSecondTaskNodeInstanceId = wi.getNodeInstanceId(); @@ -645,12 +630,14 @@ public void testBasicUserTaskProcessCancelAndRetriggerNode() throws Exception { wi = workItems.get(0); assertThat(wi.getName()).isEqualTo("SecondTask"); - assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName()); + assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName()); // since it was retriggered it must have different node instance id assertThat(wi.getNodeInstanceId()).isNotEqualTo(firstSecondTaskNodeInstanceId); - transition = handler.completeTransition(workItems.get(0).getPhaseStatus(), parameters, securityPolicy); - processInstance.transitionWorkItem(wi.getId(), transition); + userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john")); + assertThat(userTaskInstances).isNotNull().hasSize(1); + UserTaskInstance ut_2 = userTaskInstances.get(0); + ut_2.transition(COMPLETE, emptyMap(), IdentityProviders.of("john")); assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_COMPLETED); } @@ -660,7 +647,7 @@ public void testBasicUserTaskProcessClaimReleaseClaimAndCompletePhases() throws Application app = generateCodeProcessesOnly("usertask/UserTasksProcess.bpmn2"); assertThat(app).isNotNull(); - + app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class))); Process p = app.get(Processes.class).processById("UserTasksProcess"); Model m = p.createModel(); @@ -676,58 +663,36 @@ public void testBasicUserTaskProcessClaimReleaseClaimAndCompletePhases() throws assertThat(workItems).hasSize(1); WorkItem wi = workItems.get(0); assertThat(wi.getName()).isEqualTo("FirstTask"); - assertThat(wi.getPhaseStatus()).isEqualTo(ACTIVATED.getName()); + assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName()); assertThat(wi.getResults()).isEmpty(); - KogitoWorkItemHandler handler = getWorkItemHandler(p, wi); - WorkItemTransition transition = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), Map.of("ACTUAL_OWNER", "john", "test", "value"), securityPolicy); - processInstance.transitionWorkItem(workItems.get(0).getId(), transition); + UserTasks userTasks = app.get(UserTasks.class); + List userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john")); + assertThat(userTaskInstances).isNotNull().hasSize(1); + UserTaskInstance ut_1 = userTaskInstances.get(0); + ut_1.transition(CLAIM, emptyMap(), IdentityProviders.of("john")); + assertThat(ut_1.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.RESERVED); + ut_1.transition(RELEASE, emptyMap(), IdentityProviders.of("john")); + assertThat(ut_1.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.ACTIVE); + assertThat(ut_1.getActualOwner()).isNull(); + ut_1.transition(CLAIM, emptyMap(), IdentityProviders.of("john")); + assertThat(ut_1.getActualOwner()).isEqualTo("john"); + ut_1.transition(COMPLETE, emptyMap(), IdentityProviders.of("john")); assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE); - workItems = processInstance.workItems(securityPolicy); - assertThat(workItems).hasSize(1); - wi = workItems.get(0); - assertThat(wi.getName()).isEqualTo("FirstTask"); - - assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName()); - assertThat(wi.getResults()).hasSize(1) - .containsEntry("test", "value"); - - transition = handler.newTransition(TRANSITION_RESERVED_RELEASE.id(), wi.getPhaseStatus(), emptyMap(), securityPolicy); - - processInstance.transitionWorkItem(workItems.get(0).getId(), transition); - - workItems = processInstance.workItems(securityPolicy); - assertThat(workItems).hasSize(1); - wi = workItems.get(0); - assertThat(wi.getName()).isEqualTo("FirstTask"); - assertThat(wi.getPhaseStatus()).isEqualTo(ACTIVATED.getName()); - assertThat(wi.getResults()).hasSize(0); - - transition = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), Map.of("ACTUAL_OWNER", "john", "test", "value"), securityPolicy); - processInstance.transitionWorkItem(wi.getId(), transition); - assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE); - - workItems = processInstance.workItems(securityPolicy); - assertThat(workItems).hasSize(1); - wi = workItems.get(0); - assertThat(wi.getName()).isEqualTo("FirstTask"); - assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName()); - assertThat(wi.getResults()).hasSize(1) - .containsEntry("test", "value"); - - transition = handler.newTransition(TRANSITION_RESERVED_COMPLETE.id(), wi.getPhaseStatus(), emptyMap(), securityPolicy); - processInstance.transitionWorkItem(wi.getId(), transition); - assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE); - workItems = processInstance.workItems(securityPolicy); assertThat(workItems).hasSize(1); wi = workItems.get(0); assertThat(wi.getName()).isEqualTo("SecondTask"); - assertThat(wi.getPhaseStatus()).isEqualTo(RESERVED.getName()); + assertThat(wi.getPhaseStatus()).isEqualTo(UserTaskKogitoWorkItemHandler.ACTIVATED.getName()); assertThat(wi.getResults()).isEmpty(); + userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john")); + assertThat(userTaskInstances).isNotNull().hasSize(1); + UserTaskInstance ut_2 = userTaskInstances.get(0); + assertThat(ut_2.getStatus()).isEqualTo(DefaultUserTaskLifeCycle.RESERVED); + processInstance.abort(); assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ABORTED); } @@ -738,7 +703,7 @@ public void testApprovalWithReadonlyVariableTags() throws Exception { Application app = generateCodeProcessesOnly("usertask/approval-with-readonly-variable-tags.bpmn2"); assertThat(app).isNotNull(); - + app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class))); Class resourceClazz = Class.forName("org.acme.travels.ApprovalsModel", true, testClassLoader()); assertThat(resourceClazz).isNotNull(); @@ -774,7 +739,7 @@ public void testApprovalWithInternalVariableTags() throws Exception { Application app = generateCodeProcessesOnly("usertask/approval-with-internal-variable-tags.bpmn2"); assertThat(app).isNotNull(); - + app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class))); Class resourceClazz = Class.forName("org.acme.travels.ApprovalsModel", true, testClassLoader()); assertThat(resourceClazz).isNotNull(); // internal variables are not exposed on the model @@ -800,7 +765,7 @@ public void testApprovalWithRequiredVariableTags() throws Exception { Application app = generateCodeProcessesOnly("usertask/approval-with-required-variable-tags.bpmn2"); assertThat(app).isNotNull(); - + app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class))); Process p = app.get(Processes.class).processById("approvals"); Model m = p.createModel(); @@ -818,7 +783,7 @@ public void testApprovalWithIOVariableTags() throws Exception { Application app = generateCodeProcessesOnly("usertask/approval-with-io-variable-tags.bpmn2"); assertThat(app).isNotNull(); - + app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class))); Class modelClazz = Class.forName("org.acme.travels.ApprovalsModel", true, testClassLoader()); assertThat(modelClazz).isNotNull(); assertThat(modelClazz.getDeclaredField("decision")).isNotNull(); @@ -857,7 +822,7 @@ public void testUserTaskWithIOexpressionProcess() throws Exception { Application app = generateCodeProcessesOnly("usertask/UserTaskWithIOexpression.bpmn2"); assertThat(app).isNotNull(); - + app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class))); Process p = app.get(Processes.class).processById("UserTask"); Model m = p.createModel(); @@ -888,7 +853,7 @@ public void testBasicUserTaskProcessWithBusinessKey() throws Exception { Application app = generateCodeProcessesOnly("usertask/UserTasksProcess.bpmn2"); assertThat(app).isNotNull(); - + app.config().get(UserTaskConfig.class).userTaskEventListeners().listeners().add(new UserTaskKogitoWorkItemHandlerProcessListener(app.get(Processes.class))); Process p = app.get(Processes.class).processById("UserTasksProcess"); Model m = p.createModel(); @@ -912,24 +877,23 @@ public void testBasicUserTaskProcessWithBusinessKey() throws Exception { List workItems = processInstance.workItems(securityPolicy); assertThat(workItems).hasSize(1); assertThat(workItems.get(0).getName()).isEqualTo("FirstTask"); - WorkItem wi = workItems.get(0); - KogitoWorkItemHandler handler = getWorkItemHandler(p, wi); - WorkItemTransition transition = handler.newTransition(TRANSITION_ACTIVATED_CLAIM.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy); - processInstance.transitionWorkItem(workItems.get(0).getId(), transition); - - workItems = processInstance.workItems(securityPolicy); - assertThat(workItems).hasSize(1); - wi = workItems.get(0); - assertThat(wi.getName()).isEqualTo("FirstTask"); - transition = handler.newTransition(TRANSITION_RESERVED_COMPLETE.id(), wi.getPhaseStatus(), singletonMap("ACTUAL_OWNER", "john"), securityPolicy); - processInstance.transitionWorkItem(workItems.get(0).getId(), transition); + UserTasks userTasks = app.get(UserTasks.class); + List userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john")); + assertThat(userTaskInstances).isNotNull().hasSize(1); + UserTaskInstance ut_1 = userTaskInstances.get(0); + ut_1.transition(CLAIM, emptyMap(), IdentityProviders.of("john")); + ut_1.transition(COMPLETE, emptyMap(), IdentityProviders.of("john")); workItems = processInstance.workItems(securityPolicy); assertThat(workItems).hasSize(1); assertThat(workItems.get(0).getName()).isEqualTo("SecondTask"); - processInstance.completeWorkItem(workItems.get(0).getId(), null, securityPolicy); + userTaskInstances = userTasks.instances().findByIdentity(IdentityProviders.of("john")); + assertThat(userTaskInstances).isNotNull().hasSize(1); + UserTaskInstance ut_2 = userTaskInstances.get(0); + ut_2.transition(COMPLETE, emptyMap(), IdentityProviders.of("john")); + assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_COMPLETED); } diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java index 71debb2806b..86e6bd80531 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessCodegen.java @@ -42,7 +42,7 @@ import org.jbpm.compiler.canonical.ProcessMetaData; import org.jbpm.compiler.canonical.ProcessToExecModelGenerator; import org.jbpm.compiler.canonical.TriggerMetaData; -import org.jbpm.compiler.canonical.UserTaskModelMetaData; +import org.jbpm.compiler.canonical.WorkItemModelMetaData; import org.jbpm.compiler.xml.XmlProcessReader; import org.jbpm.compiler.xml.core.SemanticModules; import org.jbpm.process.core.impl.ProcessImpl; @@ -288,7 +288,7 @@ protected Collection internalGenerate() { Map processIdToInputModelGenerator = new HashMap<>(); Map processIdToOutputModelGenerator = new HashMap<>(); - Map> processIdToUserTaskModel = new HashMap<>(); + Map> processIdToWorkItemModel = new HashMap<>(); Map processIdToMetadata = new HashMap<>(); // first we generate all the data classes from variable declarations @@ -305,10 +305,13 @@ protected Collection internalGenerate() { } } - // then we generate user task inputs and outputs if any + // then we generate work items task inputs and outputs if any for (WorkflowProcess workFlowProcess : processes.values()) { - UserTasksModelClassGenerator utcg = new UserTasksModelClassGenerator(workFlowProcess); - processIdToUserTaskModel.put(workFlowProcess.getId(), utcg.generate()); + if (KogitoWorkflowProcess.SW_TYPE.equals(workFlowProcess.getType())) { + continue; + } + WorkItemModelClassGenerator utcg = new WorkItemModelClassGenerator(workFlowProcess); + processIdToWorkItemModel.put(workFlowProcess.getId(), utcg.generate()); } // then we can instantiate the exec model generator @@ -363,7 +366,7 @@ protected Collection internalGenerate() { applicationCanonicalName()); processResourceGenerator - .withUserTasks(processIdToUserTaskModel.get(workFlowProcess.getId())) + .withWorkItems(processIdToWorkItemModel.get(workFlowProcess.getId())) .withSignals(metaData.getSignals()) .withTriggers(metaData.isStartable(), metaData.isDynamic(), metaData.getTriggers()) .withTransaction(isTransactionEnabled()); @@ -421,14 +424,14 @@ protected Collection internalGenerate() { mmd.generate()); } - for (List utmd : processIdToUserTaskModel.values()) { + for (List utmd : processIdToWorkItemModel.values()) { - for (UserTaskModelMetaData ut : utmd) { - storeFile(MODEL_TYPE, UserTasksModelClassGenerator.generatedFilePath(ut.getInputModelClassName()), ut.generateInput()); + for (WorkItemModelMetaData ut : utmd) { + storeFile(MODEL_TYPE, WorkItemModelClassGenerator.generatedFilePath(ut.getInputModelClassName()), ut.generateInput()); - storeFile(MODEL_TYPE, UserTasksModelClassGenerator.generatedFilePath(ut.getOutputModelClassName()), ut.generateOutput()); + storeFile(MODEL_TYPE, WorkItemModelClassGenerator.generatedFilePath(ut.getOutputModelClassName()), ut.generateOutput()); - storeFile(MODEL_TYPE, UserTasksModelClassGenerator.generatedFilePath(ut.getTaskModelClassName()), ut.generateModel()); + storeFile(MODEL_TYPE, WorkItemModelClassGenerator.generatedFilePath(ut.getTaskModelClassName()), ut.generateModel()); } } @@ -442,7 +445,7 @@ protected Collection internalGenerate() { for (ProcessResourceGenerator resourceGenerator : rgs) { storeFile(REST_TYPE, resourceGenerator.generatedFilePath(), resourceGenerator.generate()); - storeFile(MODEL_TYPE, UserTasksModelClassGenerator.generatedFilePath(resourceGenerator.getTaskModelFactoryClassName()), resourceGenerator.getTaskModelFactory()); + storeFile(MODEL_TYPE, WorkItemModelClassGenerator.generatedFilePath(resourceGenerator.getTaskModelFactoryClassName()), resourceGenerator.getTaskModelFactory()); } } diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessResourceGenerator.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessResourceGenerator.java index 165477103f0..3e2f8765a40 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessResourceGenerator.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/ProcessResourceGenerator.java @@ -34,7 +34,7 @@ import org.drools.util.StringUtils; import org.jbpm.compiler.canonical.ProcessToExecModelGenerator; import org.jbpm.compiler.canonical.TriggerMetaData; -import org.jbpm.compiler.canonical.UserTaskModelMetaData; +import org.jbpm.compiler.canonical.WorkItemModelMetaData; import org.jbpm.ruleflow.core.Metadata; import org.jbpm.ruleflow.core.RuleFlowProcess; import org.jbpm.workflow.core.node.StartNode; @@ -88,7 +88,7 @@ public class ProcessResourceGenerator { private static final String REST_TEMPLATE_NAME = "RestResource"; private static final String REACTIVE_REST_TEMPLATE_NAME = "ReactiveRestResource"; - private static final String REST_USER_TASK_TEMPLATE_NAME = "RestResourceUserTask"; + private static final String REST_WORK_ITEM_TEMPLATE_NAME = "RestResourceWorkItem"; private static final String REST_SIGNAL_TEMPLATE_NAME = "RestResourceSignal"; private static final String SIGNAL_METHOD_PREFFIX = "signal_"; @@ -109,7 +109,7 @@ public class ProcessResourceGenerator { private boolean transactionEnabled; private List triggers; - private List userTasks; + private List workItems; private Map signals; private CompilationUnit taskModelFactoryUnit; private String taskModelFactoryClassName; @@ -134,8 +134,8 @@ public ProcessResourceGenerator( this.processClazzName = processfqcn; } - public ProcessResourceGenerator withUserTasks(List userTasks) { - this.userTasks = userTasks; + public ProcessResourceGenerator withWorkItems(List userTasks) { + this.workItems = userTasks; return this; } @@ -211,7 +211,7 @@ protected CompilationUnit getCompilationUnit() { taskModelFactoryClass.setName(taskModelFactorySimpleClassName); typeInterpolations.put("$TaskModelFactory$", taskModelFactoryClassName); - manageUserTasks(templateBuilder, template, taskModelFactoryClass, index); + manageWorkItems(templateBuilder, template, taskModelFactoryClass, index); typeInterpolations.put("$Clazz$", resourceClazzName); typeInterpolations.put("$Type$", dataClazzName); @@ -366,13 +366,13 @@ protected void generateSignalsEndpoints(TemplatedGenerator.Builder templateBuild }); } - protected void manageUserTasks(TemplatedGenerator.Builder templateBuilder, ClassOrInterfaceDeclaration template, + protected void manageWorkItems(TemplatedGenerator.Builder templateBuilder, ClassOrInterfaceDeclaration template, ClassOrInterfaceDeclaration taskModelFactoryClass, AtomicInteger index) { - if (userTasks != null && !userTasks.isEmpty()) { + if (workItems != null && !workItems.isEmpty()) { - CompilationUnit userTaskClazz = templateBuilder.build(context, REST_USER_TASK_TEMPLATE_NAME).compilationUnitOrThrow(); + CompilationUnit workItemClazz = templateBuilder.build(context, REST_WORK_ITEM_TEMPLATE_NAME).compilationUnitOrThrow(); - ClassOrInterfaceDeclaration userTaskTemplate = userTaskClazz + ClassOrInterfaceDeclaration userTaskTemplate = workItemClazz .findFirst(ClassOrInterfaceDeclaration.class) .orElseThrow(() -> new NoSuchElementException("Compilation unit doesn't contain a class or interface declaration!")); @@ -381,8 +381,8 @@ protected void manageUserTasks(TemplatedGenerator.Builder templateBuilder, Class .orElseThrow(IllegalStateException::new); SwitchStmt switchExpr = taskModelFactoryMethod.getBody().map(b -> b.findFirst(SwitchStmt.class).orElseThrow(IllegalStateException::new)).orElseThrow(IllegalStateException::new); - for (UserTaskModelMetaData userTask : userTasks) { - String methodSuffix = sanitizeName(userTask.getName()) + "_" + index.getAndIncrement(); + for (WorkItemModelMetaData workItem : workItems) { + String methodSuffix = sanitizeName(workItem.getName()) + "_" + index.getAndIncrement(); userTaskTemplate.findAll(MethodDeclaration.class).forEach(md -> { MethodDeclaration cloned = md.clone(); template.addMethod(cloned.getName() + "_" + methodSuffix, Keyword.PUBLIC) @@ -392,16 +392,16 @@ protected void manageUserTasks(TemplatedGenerator.Builder templateBuilder, Class .setAnnotations(cloned.getAnnotations()); }); - template.findAll(StringLiteralExpr.class).forEach(s -> interpolateUserTaskStrings(s, userTask)); - template.findAll(ClassOrInterfaceType.class).forEach(c -> interpolateUserTaskTypes(c, userTask)); - template.findAll(NameExpr.class).forEach(c -> interpolateUserTaskNameExp(c, userTask)); - if (!userTask.isAdHoc()) { + template.findAll(StringLiteralExpr.class).forEach(s -> interpolateUserTaskStrings(s, workItem)); + template.findAll(ClassOrInterfaceType.class).forEach(c -> interpolateUserTaskTypes(c, workItem)); + template.findAll(NameExpr.class).forEach(c -> interpolateUserTaskNameExp(c, workItem)); + if (!workItem.isAdHoc()) { template.findAll(MethodDeclaration.class) .stream() .filter(md -> md.getNameAsString().equals(SIGNAL_METHOD_PREFFIX + methodSuffix)) .forEach(template::remove); } - switchExpr.getEntries().add(0, userTask.getModelSwitchEntry()); + switchExpr.getEntries().add(0, workItem.getModelSwitchEntry()); } } @@ -479,14 +479,14 @@ private void interpolateStrings(StringLiteralExpr vv) { vv.setString(interpolated); } - private void interpolateUserTaskStrings(StringLiteralExpr vv, UserTaskModelMetaData userTask) { + private void interpolateUserTaskStrings(StringLiteralExpr vv, WorkItemModelMetaData userTask) { String s = vv.getValue(); String interpolated = s.replace("$taskName$", sanitizeName(userTask.getName())); interpolated = interpolated.replace("$taskNodeName$", userTask.getNodeName()); vv.setString(interpolated); } - private void interpolateUserTaskNameExp(NameExpr name, UserTaskModelMetaData userTask) { + private void interpolateUserTaskNameExp(NameExpr name, WorkItemModelMetaData userTask) { name.setName(userTask.templateReplacement(name.getNameAsString())); } @@ -497,7 +497,7 @@ private void interpolateMethods(MethodDeclaration m) { m.setName(interpolated); } - private void interpolateUserTaskTypes(Type t, UserTaskModelMetaData userTask) { + private void interpolateUserTaskTypes(Type t, WorkItemModelMetaData userTask) { if (t.isArrayType()) { t = t.asArrayType().getElementType(); } @@ -508,11 +508,11 @@ private void interpolateUserTaskTypes(Type t, UserTaskModelMetaData userTask) { } } - private void interpolateUserTaskTypes(SimpleName returnType, UserTaskModelMetaData userTask) { + private void interpolateUserTaskTypes(SimpleName returnType, WorkItemModelMetaData userTask) { returnType.setIdentifier(userTask.templateReplacement(returnType.getIdentifier())); } - private void interpolateUserTaskTypeArguments(NodeList ta, UserTaskModelMetaData userTask) { + private void interpolateUserTaskTypeArguments(NodeList ta, WorkItemModelMetaData userTask) { ta.stream().forEach(t -> interpolateUserTaskTypes(t, userTask)); } diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/UserTasksModelClassGenerator.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/WorkItemModelClassGenerator.java similarity index 81% rename from kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/UserTasksModelClassGenerator.java rename to kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/WorkItemModelClassGenerator.java index a3850d60f1a..e66be467caf 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/UserTasksModelClassGenerator.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/WorkItemModelClassGenerator.java @@ -21,21 +21,21 @@ import java.util.List; import org.jbpm.compiler.canonical.ProcessToExecModelGenerator; -import org.jbpm.compiler.canonical.UserTaskModelMetaData; +import org.jbpm.compiler.canonical.WorkItemModelMetaData; import org.kie.api.definition.process.WorkflowProcess; -public class UserTasksModelClassGenerator { +public class WorkItemModelClassGenerator { private final WorkflowProcess workFlowProcess; - private List modelMetaData; + private List modelMetaData; - public UserTasksModelClassGenerator(WorkflowProcess workFlowProcess) { + public WorkItemModelClassGenerator(WorkflowProcess workFlowProcess) { this.workFlowProcess = workFlowProcess; } - public List generate() { + public List generate() { // create model class for all variables - modelMetaData = ProcessToExecModelGenerator.INSTANCE.generateUserTaskModel(workFlowProcess); + modelMetaData = ProcessToExecModelGenerator.INSTANCE.generateWorkItemModel(workFlowProcess); return modelMetaData; } diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskCodegen.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskCodegen.java index 58bd4a54b61..bd10069d470 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskCodegen.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskCodegen.java @@ -18,10 +18,13 @@ */ package org.kie.kogito.codegen.usertask; +import java.io.File; import java.io.IOException; import java.io.Reader; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -74,6 +77,7 @@ import static org.kie.kogito.serverless.workflow.utils.ServerlessWorkflowUtils.FAIL_ON_ERROR_PROPERTY; public class UserTaskCodegen extends AbstractGenerator { + private static final String NODE_NAME = "NodeName"; private static final String DESCRIPTION = "Description"; private static final String PRIORITY = "Priority"; @@ -97,6 +101,8 @@ public class UserTaskCodegen extends AbstractGenerator { TemplatedGenerator templateGenerator; private List descriptors; + private TemplatedGenerator producerTemplateGenerator; + private TemplatedGenerator restTemplateGenerator; public UserTaskCodegen(KogitoBuildContext context, List collectedResources) { super(context, "usertasks"); @@ -106,6 +112,16 @@ public UserTaskCodegen(KogitoBuildContext context, List collectedResources .withTemplateBasePath("/class-templates/usertask") .withTargetTypeName(SECTION_CLASS_NAME) .build(context, "UserTask"); + + producerTemplateGenerator = TemplatedGenerator.builder() + .withTemplateBasePath("/class-templates/usertask") + .withTargetTypeName(SECTION_CLASS_NAME) + .build(context, "UserTasksServiceProducer"); + + restTemplateGenerator = TemplatedGenerator.builder() + .withTemplateBasePath("/class-templates/usertask") + .withTargetTypeName(SECTION_CLASS_NAME) + .build(context, "RestResourceUserTask"); } @Override @@ -125,6 +141,42 @@ public boolean isEmpty() { @Override protected Collection internalGenerate() { + if (descriptors.isEmpty()) { + return Collections.emptyList(); + } + + List generatedFiles = new ArrayList<>(); + generatedFiles.addAll(generateUserTask()); + if (context().hasDI()) { + generatedFiles.add(generateProducer()); + } + + if (context().hasRESTForGenerator(this)) { + generatedFiles.add(generateRestEndpiont()); + } + + return generatedFiles; + } + + private GeneratedFile generateRestEndpiont() { + String packageName = context().getPackageName(); + CompilationUnit compilationUnit = producerTemplateGenerator.compilationUnitOrThrow("Not rest endpoints template found for user tasks"); + compilationUnit.setPackageDeclaration(packageName); + String className = compilationUnit.findFirst(ClassOrInterfaceDeclaration.class).get().getNameAsString(); + String urlBase = packageName.replaceAll("\\.", File.separator); + return new GeneratedFile(GeneratedFileType.SOURCE, Path.of(urlBase).resolve(className + ".java"), compilationUnit.toString()); + } + + private GeneratedFile generateProducer() { + String packageName = context().getPackageName(); + CompilationUnit compilationUnit = restTemplateGenerator.compilationUnitOrThrow("No producer template found for user tasks"); + compilationUnit.setPackageDeclaration(packageName); + String className = compilationUnit.findFirst(ClassOrInterfaceDeclaration.class).get().getNameAsString(); + String urlBase = packageName.replaceAll("\\.", File.separator); + return new GeneratedFile(GeneratedFileType.SOURCE, Path.of(urlBase).resolve(className + ".java"), compilationUnit.toString()); + } + + private List generateUserTask() { List generatedFiles = new ArrayList<>(); for (Work info : descriptors) { CompilationUnit unit = templateGenerator.compilationUnit().get(); @@ -171,7 +223,6 @@ protected Collection internalGenerate() { generatedFiles.add(new GeneratedFile(GeneratedFileType.SOURCE, UserTaskCodegenHelper.path(info).resolve(className + ".java"), unit.toString())); } - return generatedFiles; } diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskConfigGenerator.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskConfigGenerator.java index 641f3b2d1a5..d6483f92b28 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskConfigGenerator.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/usertask/UserTaskConfigGenerator.java @@ -35,11 +35,9 @@ public class UserTaskConfigGenerator implements ConfigGenerator { - private List collectedResources; private TemplatedGenerator templateGenerator; public UserTaskConfigGenerator(KogitoBuildContext context, List collectedResources) { - this.collectedResources = collectedResources; templateGenerator = TemplatedGenerator.builder() .withTemplateBasePath("/class-templates/usertask") .build(context, "UserTaskConfig"); @@ -54,7 +52,6 @@ public String configClassName() { public GeneratedFile generate() { CompilationUnit unit = templateGenerator.compilationUnit().get(); String packageName = unit.getPackageDeclaration().get().getNameAsString(); - unit.getPackageDeclaration().get().setName(packageName); ClassOrInterfaceDeclaration clazzDeclaration = unit.findFirst(ClassOrInterfaceDeclaration.class).get(); clazzDeclaration.setName(configClassName()); diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceUserTaskQuarkusTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceUserTaskQuarkusTemplate.java deleted file mode 100644 index 36f93ac4624..00000000000 --- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceUserTaskQuarkusTemplate.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package com.myspace.demo; - -import java.util.List; - -import jakarta.ws.rs.NotFoundException; -import jakarta.ws.rs.core.Context; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.UriInfo; - -import org.kie.kogito.auth.IdentityProviders; -import org.kie.kogito.auth.SecurityPolicy; -import org.kie.kogito.process.Process; -import org.kie.kogito.process.ProcessInstance; -import org.kie.kogito.process.ProcessInstanceReadMode; -import org.kie.kogito.process.WorkItem; -import org.kie.kogito.process.impl.Sig; -import org.kie.kogito.services.uow.UnitOfWorkExecutor; - -public class $Type$Resource { - - @POST - @Path("/{id}/$taskName$") - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - public Response signal(@PathParam("id") final String id, - @QueryParam("user") final String user, - @QueryParam("group") final List groups, - @Context UriInfo uriInfo) { - return processService.signalWorkItem(process, id, "$taskName$", SecurityPolicy.of(user, groups)) - .map(task -> Response - .created(uriInfo.getAbsolutePathBuilder().path(task.getId()).build()) - .entity(task.getResults()) - .build()) - .orElseThrow(NotFoundException::new); - } - - @POST - @Path("/{id}/$taskName$/{taskId}") - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - public $Type$Output completeTask(@PathParam("id") final String id, - @PathParam("taskId") final String taskId, - @QueryParam("phase") @DefaultValue("complete") final String phase, - @QueryParam("user") final String user, - @QueryParam("group") final List groups, - final $TaskOutput$ model) { - return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), model) - .orElseThrow(NotFoundException::new); - } - - @PUT - @Path("/{id}/$taskName$/{taskId}") - @Consumes(MediaType.APPLICATION_JSON) - public $TaskOutput$ saveTask(@PathParam("id") final String id, - @PathParam("taskId") final String taskId, - @QueryParam("user") final String user, - @QueryParam("group") final List groups, - final $TaskOutput$ model) { - return processService.setWorkItemOutput(process, id, taskId, SecurityPolicy.of(user, groups), model, $TaskOutput$::fromMap) - .orElseThrow(NotFoundException::new); - } - - @POST - @Path("/{id}/$taskName$/{taskId}/phases/{phase}") - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - public $Type$Output taskTransition( - @PathParam("id") final String id, - @PathParam("taskId") final String taskId, - @PathParam("phase") final String phase, - @QueryParam("user") final String user, - @QueryParam("group") final List groups, - final $TaskOutput$ model) { - return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), model) - .orElseThrow(NotFoundException::new); - } - - @GET - @Path("/{id}/$taskName$/{taskId}") - @Produces(MediaType.APPLICATION_JSON) - public $TaskModel$ getWorkItem(@PathParam("id") String id, - @PathParam("taskId") String taskId, - @QueryParam("user") final String user, - @QueryParam("group") final List groups) { - return processService.getWorkItem(process, id, taskId, SecurityPolicy.of(user, groups), $TaskModel$::from) - .orElseThrow(NotFoundException::new); - } - - @DELETE - @Path("/{id}/$taskName$/{taskId}") - @Produces(MediaType.APPLICATION_JSON) - public $Type$Output abortTask(@PathParam("id") final String id, - @PathParam("taskId") final String taskId, - @QueryParam("phase") @DefaultValue("abort") final String phase, - @QueryParam("user") final String user, - @QueryParam("group") final List groups) { - return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), null) - .orElseThrow(NotFoundException::new); - } - - @GET - @Path("$taskName$/schema") - @Produces(MediaType.APPLICATION_JSON) - public Map getSchema() { - return JsonSchemaUtil.load(this.getClass().getClassLoader(), process.id(), "$taskName$"); - } - - @GET - @Path("/{id}/$taskName$/{taskId}/schema") - @Produces(MediaType.APPLICATION_JSON) - public Map getSchemaAndPhases(@PathParam("id") final String id, - @PathParam("taskId") final String taskId, - @QueryParam("user") final String user, - @QueryParam("group") final List groups) { - return processService.getWorkItemSchemaAndPhases(process, id, taskId, "$taskName$", SecurityPolicy.of(user, groups)); - } - - @POST - @Path("/{id}/$taskName$/{taskId}/comments") - @Consumes(MediaType.TEXT_PLAIN) - @Produces(MediaType.APPLICATION_JSON) - public Response addComment(@PathParam("id") final String id, - @PathParam("taskId") final String taskId, - @QueryParam("user") final String user, - @QueryParam("group") final List groups, - String commentInfo, - @Context UriInfo uriInfo) { - return processService.addComment(process, id, taskId, SecurityPolicy.of(user, groups), commentInfo) - .map(comment -> Response.created(uriInfo.getAbsolutePathBuilder().path(comment.getId().toString()).build()) - .entity(comment).build()) - .orElseThrow(NotFoundException::new); - } - - @PUT - @Path("/{id}/$taskName$/{taskId}/comments/{commentId}") - @Consumes(MediaType.TEXT_PLAIN) - @Produces(MediaType.APPLICATION_JSON) - public Comment updateComment(@PathParam("id") final String id, - @PathParam("taskId") final String taskId, - @PathParam("commentId") final String commentId, - @QueryParam("user") final String user, - @QueryParam("group") final List groups, - String comment) { - return processService.updateComment(process, id, taskId, commentId, SecurityPolicy.of(user, groups), comment) - .orElseThrow(NotFoundException::new); - } - - @DELETE - @Path("/{id}/$taskName$/{taskId}/comments/{commentId}") - public Response deleteComment(@PathParam("id") final String id, - @PathParam("taskId") final String taskId, - @PathParam("commentId") final String commentId, - @QueryParam("user") final String user, - @QueryParam("group") final List groups) { - return processService.deleteComment(process, id, taskId, commentId, SecurityPolicy.of(user, groups)) - .map(removed -> (removed ? Response.ok() : Response.status(Status.NOT_FOUND)).build()) - .orElseThrow(NotFoundException::new); - } - - @POST - @Path("/{id}/$taskName$/{taskId}/attachments") - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - public Response addAttachment(@PathParam("id") final String id, - @PathParam("taskId") final String taskId, - @QueryParam("user") final String user, - @QueryParam("group") final List groups, - AttachmentInfo attachmentInfo, - @Context UriInfo uriInfo) { - return processService.addAttachment(process, id, taskId, SecurityPolicy.of(user, groups), attachmentInfo) - .map(attachment -> Response - .created(uriInfo.getAbsolutePathBuilder().path(attachment.getId().toString()).build()) - .entity(attachment).build()) - .orElseThrow(NotFoundException::new); - } - - @PUT - @Path("/{id}/$taskName$/{taskId}/attachments/{attachmentId}") - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - public Attachment updateAttachment(@PathParam("id") final String id, - @PathParam("taskId") final String taskId, - @PathParam("attachmentId") final String attachmentId, - @QueryParam("user") final String user, - @QueryParam("group") final List groups, - AttachmentInfo attachment) { - return processService.updateAttachment(process, id, taskId, attachmentId, SecurityPolicy.of(user, groups), attachment) - .orElseThrow(NotFoundException::new); - } - - @DELETE - @Path("/{id}/$taskName$/{taskId}/attachments/{attachmentId}") - public Response deleteAttachment(@PathParam("id") final String id, - @PathParam("taskId") final String taskId, - @PathParam("attachmentId") final String attachmentId, - @QueryParam("user") final String user, - @QueryParam("group") final List groups) { - return processService.deleteAttachment(process, id, taskId, attachmentId, SecurityPolicy.of(user, groups)) - .map(removed -> (removed ? Response.ok() : Response.status(Status.NOT_FOUND)).build()) - .orElseThrow(NotFoundException::new); - } - - @GET - @Path("/{id}/$taskName$/{taskId}/attachments/{attachmentId}") - @Produces(MediaType.APPLICATION_JSON) - public Attachment getAttachment(@PathParam("id") final String id, - @PathParam("taskId") final String taskId, - @PathParam("attachmentId") final String attachmentId, - @QueryParam("user") final String user, - @QueryParam("group") final List groups) { - return processService.getAttachment(process, id, taskId, attachmentId, SecurityPolicy.of(user, groups)) - .orElseThrow(() -> new NotFoundException("Attachment " + attachmentId + " not found")); - } - - @GET - @Path("/{id}/$taskName$/{taskId}/attachments") - @Produces(MediaType.APPLICATION_JSON) - public Collection getAttachments(@PathParam("id") final String id, - @PathParam("taskId") final String taskId, - @QueryParam("user") final String user, - @QueryParam("group") final List groups) { - return processService.getAttachments(process, id, taskId, SecurityPolicy.of(user, groups)) - .orElseThrow(NotFoundException::new); - } - - @GET - @Path("/{id}/$taskName$/{taskId}/comments/{commentId}") - @Produces(MediaType.APPLICATION_JSON) - public Comment getComment(@PathParam("id") final String id, - @PathParam("taskId") final String taskId, - @PathParam("commentId") final String commentId, - @QueryParam("user") final String user, - @QueryParam("group") final List groups) { - return processService.getComment(process, id, taskId, commentId, SecurityPolicy.of(user, groups)) - .orElseThrow(() -> new NotFoundException("Comment " + commentId + " not found")); - } - - @GET - @Path("/{id}/$taskName$/{taskId}/comments") - @Produces(MediaType.APPLICATION_JSON) - public Collection getComments(@PathParam("id") final String id, - @PathParam("taskId") final String taskId, - @QueryParam("user") final String user, - @QueryParam("group") final List groups) { - return processService.getComments(process, id, taskId, SecurityPolicy.of(user, groups)) - .orElseThrow(NotFoundException::new); - } -} diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceUserTaskSpringTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceUserTaskSpringTemplate.java deleted file mode 100644 index a18e56c3379..00000000000 --- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceUserTaskSpringTemplate.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package com.myspace.demo; - -import java.util.List; -import java.util.Map; - -import org.jbpm.util.JsonSchemaUtil; -import org.kie.kogito.auth.IdentityProviders; -import org.kie.kogito.auth.SecurityPolicy; -import org.kie.kogito.process.ProcessInstance; -import org.kie.kogito.process.WorkItem; -import org.kie.kogito.process.impl.Sig; -import org.kie.kogito.process.workitem.Comment; -import org.kie.kogito.process.workitem.TaskMetaInfo; -import org.kie.kogito.services.uow.UnitOfWorkExecutor; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.util.UriComponentsBuilder; - -public class $Type$Resource { - - @PostMapping(value = "/{id}/$taskName$", produces = MediaType.APPLICATION_JSON_VALUE, - consumes = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity signal(@PathVariable("id") final String id, - @RequestParam("user") final String user, - @RequestParam("group") final List groups, - final UriComponentsBuilder uriComponentsBuilder) { - - return processService.signalWorkItem(process, id, "$taskName$", SecurityPolicy.of(user, groups)) - .map(task -> ResponseEntity - .created(uriComponentsBuilder - .path("/$name$/{id}/$taskName$/{taskId}") - .buildAndExpand(id, task.getId()).toUri()) - .body(task.getResults())) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - } - - @PostMapping(value = "/{id}/$taskName$/{taskId}/phases/{phase}", produces = MediaType.APPLICATION_JSON_VALUE, - consumes = MediaType.APPLICATION_JSON_VALUE) - public $Type$Output completeTask(@PathVariable("id") final String id, - @PathVariable("taskId") final String taskId, - @PathVariable("phase") final String phase, - @RequestParam("user") final String user, - @RequestParam("group") final List groups, - @RequestBody(required = false) final $TaskOutput$ model) { - return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), model) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - } - - @PutMapping(value = "/{id}/$taskName$/{taskId}", consumes = MediaType.APPLICATION_JSON_VALUE) - public $TaskOutput$ saveTask(@PathVariable("id") final String id, - @PathVariable("taskId") final String taskId, - @RequestParam(value = "user", required = false) final String user, - @RequestParam(value = "group", required = false) final List groups, - @RequestBody(required = false) final $TaskOutput$ model) { - return processService.setWorkItemOutput(process, id, taskId, SecurityPolicy.of(user, groups), model, $TaskOutput$::fromMap) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - } - - @PostMapping(value = "/{id}/$taskName$/{taskId}", produces = MediaType.APPLICATION_JSON_VALUE, - consumes = MediaType.APPLICATION_JSON_VALUE) - public $Type$Output taskTransition(@PathVariable("id") final String id, - @PathVariable("taskId") final String taskId, - @RequestParam(value = "phase", required = false, - defaultValue = "complete") final String phase, - @RequestParam(value = "user", - required = false) final String user, - @RequestParam(value = "group", - required = false) final List groups, - @RequestBody(required = false) final $TaskOutput$ model) { - return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), model) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - } - - @GetMapping(value = "/{id}/$taskName$/{taskId}", produces = MediaType.APPLICATION_JSON_VALUE) - public $TaskModel$ getTask(@PathVariable("id") String id, - @PathVariable("taskId") String taskId, - @RequestParam(value = "user", required = false) final String user, - @RequestParam(value = "group", - required = false) final List groups) { - return processService.getWorkItem(process, id, taskId, SecurityPolicy.of(user, groups), $TaskModel$::from) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - } - - @DeleteMapping(value = "/{id}/$taskName$/{taskId}", produces = MediaType.APPLICATION_JSON_VALUE) - public $Type$Output abortTask(@PathVariable("id") final String id, - @PathVariable("taskId") final String taskId, - @RequestParam(value = "phase", required = false, - defaultValue = "abort") final String phase, - @RequestParam(value = "user", required = false) final String user, - @RequestParam(value = "group", - required = false) final List groups) { - return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), null) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - } - - @GetMapping(value = "$taskName$/schema", produces = MediaType.APPLICATION_JSON_VALUE) - public Map getSchema() { - return JsonSchemaUtil.load(this.getClass().getClassLoader(), process.id(), "$taskName$"); - } - - @GetMapping(value = "/{id}/$taskName$/{taskId}/schema", produces = MediaType.APPLICATION_JSON_VALUE) - public Map getSchemaAndPhases(@PathVariable("id") final String id, - @PathVariable("taskId") final String taskId, - @RequestParam(value = "user", required = false) final String user, - @RequestParam(value = "group", - required = false) final List groups) { - return processService.getWorkItemSchemaAndPhases(process, id, taskId, "$taskName$", SecurityPolicy.of(user, groups)); - } - - @PostMapping(value = "/{id}/$taskName$/{taskId}/comments", produces = MediaType.APPLICATION_JSON_VALUE, - consumes = MediaType.TEXT_PLAIN_VALUE) - public ResponseEntity addComment(@PathVariable("id") final String id, - @PathVariable("taskId") final String taskId, - @RequestParam(value = "user", required = false) final String user, - @RequestParam(value = "group", - required = false) final List groups, - @RequestBody String commentInfo, - UriComponentsBuilder uriComponentsBuilder) { - return processService.addComment(process, id, taskId, SecurityPolicy.of(user, groups), commentInfo) - .map(comment -> ResponseEntity - .created(uriComponentsBuilder.path("/$name$/{id}/$taskName$/{taskId}/comments/{commentId}") - .buildAndExpand(id, taskId, comment.getId().toString()).toUri()) - .body(comment)) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - } - - @PutMapping(value = "/{id}/$taskName$/{taskId}/comments/{commentId}", produces = MediaType.APPLICATION_JSON_VALUE, - consumes = MediaType.TEXT_PLAIN_VALUE) - public Comment updateComment(@PathVariable("id") final String id, - @PathVariable("taskId") final String taskId, - @PathVariable("commentId") final String commentId, - @RequestParam(value = "user", required = false) final String user, - @RequestParam(value = "group", - required = false) final List groups, - @RequestBody String comment) { - return processService.updateComment(process, id, taskId, commentId, SecurityPolicy.of(user, groups), comment) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - } - - @DeleteMapping(value = "/{id}/$taskName$/{taskId}/comments/{commentId}") - public ResponseEntity deleteComment(@PathVariable("id") final String id, - @PathVariable("taskId") final String taskId, - @PathVariable("commentId") final String commentId, - @RequestParam(value = "user", required = false) final String user, - @RequestParam(value = "group", required = false) final List groups) { - return processService.deleteComment(process, id, taskId, commentId, SecurityPolicy.of(user, groups)) - .map(removed -> (removed ? ResponseEntity.ok().build() : ResponseEntity.notFound().build())) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - } - - @PostMapping(value = "/{id}/$taskName$/{taskId}/attachments", produces = MediaType.APPLICATION_JSON_VALUE, - consumes = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity addAttachment(@PathVariable("id") final String id, - @PathVariable("taskId") final String taskId, - @RequestParam(value = "user", required = false) final String user, - @RequestParam(value = "group", - required = false) final List groups, - @RequestBody AttachmentInfo attachmentInfo, - UriComponentsBuilder uriComponentsBuilder) { - return processService.addAttachment(process, id, taskId, SecurityPolicy.of(user, groups), attachmentInfo) - .map(attachment -> ResponseEntity - .created(uriComponentsBuilder.path( - "/$name$/{id}/$taskName$/{taskId}/attachments/{attachmentId}") - .buildAndExpand(id, - taskId, attachment.getId()) - .toUri()) - .body(attachment)) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - } - - @PutMapping(value = "/{id}/$taskName$/{taskId}/attachments/{attachmentId}", - produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE) - public Attachment updateAttachment(@PathVariable("id") final String id, - @PathVariable("taskId") final String taskId, - @PathVariable("attachmentId") final String attachmentId, - @RequestParam(value = "user", - required = false) final String user, - @RequestParam(value = "group", - required = false) final List groups, - @RequestBody AttachmentInfo attachment) { - return processService.updateAttachment(process, id, taskId, attachmentId, SecurityPolicy.of(user, groups), attachment) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - } - - @DeleteMapping(value = "/{id}/$taskName$/{taskId}/attachments/{attachmentId}") - public ResponseEntity deleteAttachment(@PathVariable("id") final String id, - @PathVariable("taskId") final String taskId, - @PathVariable("attachmentId") final String attachmentId, - @RequestParam(value = "user", required = false) final String user, - @RequestParam(value = "group", required = false) final List groups) { - - return processService.deleteAttachment(process, id, taskId, attachmentId, SecurityPolicy.of(user, groups)) - .map(removed -> (removed ? ResponseEntity.ok() : ResponseEntity.notFound()).build()) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - } - - @GetMapping(value = "/{id}/$taskName$/{taskId}/attachments/{attachmentId}", produces = MediaType.APPLICATION_JSON_VALUE) - public Attachment getAttachment(@PathVariable("id") final String id, - @PathVariable("taskId") final String taskId, - @PathVariable("attachmentId") final String attachmentId, - @RequestParam(value = "user", required = false) final String user, - @RequestParam(value = "group", required = false) final List groups) { - return processService.getAttachment(process, id, taskId, attachmentId, SecurityPolicy.of(user, groups)) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Attachment " + attachmentId + " not found")); - } - - @GetMapping(value = "/{id}/$taskName$/{taskId}/attachments", produces = MediaType.APPLICATION_JSON_VALUE) - public Collection getAttachments(@PathVariable("id") final String id, - @PathVariable("taskId") final String taskId, - @RequestParam(value = "user") final String user, - @RequestParam(value = "group") final List groups) { - return processService.getAttachments(process, id, taskId, SecurityPolicy.of(user, groups)) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - } - - @GetMapping(value = "/{id}/$taskName$/{taskId}/comments/{commentId}", produces = MediaType.APPLICATION_JSON_VALUE) - public Comment getComment(@PathVariable("id") final String id, - @PathVariable("taskId") final String taskId, - @PathVariable("commentId") final String commentId, - @RequestParam(value = "user", required = false) final String user, - @RequestParam(value = "group", required = false) final List groups) { - return processService.getComment(process, id, taskId, commentId, SecurityPolicy.of(user, groups)) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Comment " + commentId + " not found")); - } - - @GetMapping(value = "/{id}/$taskName$/{taskId}/comments", produces = MediaType.APPLICATION_JSON_VALUE) - public Collection getComments(@PathVariable("id") final String id, - @PathVariable("taskId") final String taskId, - @RequestParam(value = "user", required = false) final String user, - @RequestParam(value = "group", required = false) final List groups) { - return processService.getComments(process, id, taskId, SecurityPolicy.of(user, groups)) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - } -} \ No newline at end of file diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceWorkItemQuarkusTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceWorkItemQuarkusTemplate.java new file mode 100644 index 00000000000..2bf6dd3bc69 --- /dev/null +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceWorkItemQuarkusTemplate.java @@ -0,0 +1,139 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.myspace.demo; + +import java.util.List; + +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriInfo; + +import org.kie.kogito.auth.IdentityProviders; +import org.kie.kogito.auth.SecurityPolicy; +import org.kie.kogito.process.Process; +import org.kie.kogito.process.ProcessInstance; +import org.kie.kogito.process.ProcessInstanceReadMode; +import org.kie.kogito.process.WorkItem; +import org.kie.kogito.process.impl.Sig; +import org.kie.kogito.services.uow.UnitOfWorkExecutor; +import org.kie.kogito.usertask.UserTaskService; + +import jakarta.inject.Inject; + +public class $Type$Resource { + + @POST + @Path("/{id}/$taskName$/trigger") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response signal(@PathParam("id") final String id, + @QueryParam("user") final String user, + @QueryParam("group") final List groups, + @Context UriInfo uriInfo) { + return processService.signalWorkItem(process, id, "$taskName$", SecurityPolicy.of(user, groups)) + .map(task -> Response + .created(uriInfo.getAbsolutePathBuilder().path(task.getId()).build()) + .entity(task.getResults()) + .build()) + .orElseThrow(NotFoundException::new); + } + + @POST + @Path("/{id}/$taskName$/{taskId}") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public $Type$Output completeTask(@PathParam("id") final String id, + @PathParam("taskId") final String taskId, + @QueryParam("phase") @DefaultValue("complete") final String phase, + @QueryParam("user") final String user, + @QueryParam("group") final List groups, + final $TaskOutput$ model) { + return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), model) + .orElseThrow(NotFoundException::new); + } + + @PUT + @Path("/{id}/$taskName$/{taskId}") + @Consumes(MediaType.APPLICATION_JSON) + public $TaskOutput$ saveTask(@PathParam("id") final String id, + @PathParam("taskId") final String taskId, + @QueryParam("user") final String user, + @QueryParam("group") final List groups, + final $TaskOutput$ model) { + return processService.setWorkItemOutput(process, id, taskId, SecurityPolicy.of(user, groups), model, $TaskOutput$::fromMap) + .orElseThrow(NotFoundException::new); + } + + @POST + @Path("/{id}/$taskName$/{taskId}/phases/{phase}") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public $Type$Output taskTransition( + @PathParam("id") final String id, + @PathParam("taskId") final String taskId, + @PathParam("phase") final String phase, + @QueryParam("user") final String user, + @QueryParam("group") final List groups, + final $TaskOutput$ model) { + return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), model) + .orElseThrow(NotFoundException::new); + } + + @GET + @Path("/{id}/$taskName$/{taskId}") + @Produces(MediaType.APPLICATION_JSON) + public $TaskModel$ getWorkItem(@PathParam("id") String id, + @PathParam("taskId") String taskId, + @QueryParam("user") final String user, + @QueryParam("group") final List groups) { + return processService.getWorkItem(process, id, taskId, SecurityPolicy.of(user, groups), $TaskModel$::from) + .orElseThrow(NotFoundException::new); + } + + @DELETE + @Path("/{id}/$taskName$/{taskId}") + @Produces(MediaType.APPLICATION_JSON) + public $Type$Output abortTask(@PathParam("id") final String id, + @PathParam("taskId") final String taskId, + @QueryParam("phase") @DefaultValue("abort") final String phase, + @QueryParam("user") final String user, + @QueryParam("group") final List groups) { + return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), null) + .orElseThrow(NotFoundException::new); + } + + @GET + @Path("$taskName$/schema") + @Produces(MediaType.APPLICATION_JSON) + public Map getSchema() { + return JsonSchemaUtil.load(this.getClass().getClassLoader(), process.id(), "$taskName$"); + } + + @GET + @Path("/{id}/$taskName$/{taskId}/schema") + @Produces(MediaType.APPLICATION_JSON) + public Map getSchemaAndPhases(@PathParam("id") final String id, + @PathParam("taskId") final String taskId, + @QueryParam("user") final String user, + @QueryParam("group") final List groups) { + return processService.getWorkItemSchemaAndPhases(process, id, taskId, "$taskName$", SecurityPolicy.of(user, groups)); + } + +} diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceWorkItemSpringTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceWorkItemSpringTemplate.java new file mode 100644 index 00000000000..7a5e65aea26 --- /dev/null +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/RestResourceWorkItemSpringTemplate.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.myspace.demo; + +import java.util.List; +import java.util.Map; + +import org.jbpm.util.JsonSchemaUtil; +import org.kie.kogito.auth.IdentityProviders; +import org.kie.kogito.auth.SecurityPolicy; +import org.kie.kogito.process.ProcessInstance; +import org.kie.kogito.process.WorkItem; +import org.kie.kogito.process.impl.Sig; +import org.kie.kogito.process.workitem.Comment; +import org.kie.kogito.process.workitem.TaskMetaInfo; +import org.kie.kogito.services.uow.UnitOfWorkExecutor; +import org.kie.kogito.usertask.UserTaskService; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.beans.factory.annotation.Autowired; + +public class $Type$Resource { + + @PostMapping(value = "/{id}/$taskName$/trigger", produces = MediaType.APPLICATION_JSON_VALUE, + consumes = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity signal(@PathVariable("id") final String id, + @RequestParam("user") final String user, + @RequestParam("group") final List groups, + final UriComponentsBuilder uriComponentsBuilder) { + + return processService.signalWorkItem(process, id, "$taskName$", SecurityPolicy.of(user, groups)) + .map(task -> ResponseEntity + .created(uriComponentsBuilder + .path("/$name$/{id}/$taskName$/{taskId}") + .buildAndExpand(id, task.getId()).toUri()) + .body(task.getResults())) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + } + + @PostMapping(value = "/{id}/$taskName$/{taskId}/phases/{phase}", produces = MediaType.APPLICATION_JSON_VALUE, + consumes = MediaType.APPLICATION_JSON_VALUE) + public $Type$Output completeTask(@PathVariable("id") final String id, + @PathVariable("taskId") final String taskId, + @PathVariable("phase") final String phase, + @RequestParam("user") final String user, + @RequestParam("group") final List groups, + @RequestBody(required = false) final $TaskOutput$ model) { + return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), model) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + } + + @PutMapping(value = "/{id}/$taskName$/{taskId}", consumes = MediaType.APPLICATION_JSON_VALUE) + public $TaskOutput$ saveTask(@PathVariable("id") final String id, + @PathVariable("taskId") final String taskId, + @RequestParam(value = "user", required = false) final String user, + @RequestParam(value = "group", required = false) final List groups, + @RequestBody(required = false) final $TaskOutput$ model) { + return processService.setWorkItemOutput(process, id, taskId, SecurityPolicy.of(user, groups), model, $TaskOutput$::fromMap) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + } + + @PostMapping(value = "/{id}/$taskName$/{taskId}", produces = MediaType.APPLICATION_JSON_VALUE, + consumes = MediaType.APPLICATION_JSON_VALUE) + public $Type$Output taskTransition(@PathVariable("id") final String id, + @PathVariable("taskId") final String taskId, + @RequestParam(value = "phase", required = false, + defaultValue = "complete") final String phase, + @RequestParam(value = "user", + required = false) final String user, + @RequestParam(value = "group", + required = false) final List groups, + @RequestBody(required = false) final $TaskOutput$ model) { + return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), model) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + } + + @GetMapping(value = "/{id}/$taskName$/{taskId}", produces = MediaType.APPLICATION_JSON_VALUE) + public $TaskModel$ getTask(@PathVariable("id") String id, + @PathVariable("taskId") String taskId, + @RequestParam(value = "user", required = false) final String user, + @RequestParam(value = "group", + required = false) final List groups) { + return processService.getWorkItem(process, id, taskId, SecurityPolicy.of(user, groups), $TaskModel$::from) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + } + + @DeleteMapping(value = "/{id}/$taskName$/{taskId}", produces = MediaType.APPLICATION_JSON_VALUE) + public $Type$Output abortTask(@PathVariable("id") final String id, + @PathVariable("taskId") final String taskId, + @RequestParam(value = "phase", required = false, + defaultValue = "abort") final String phase, + @RequestParam(value = "user", required = false) final String user, + @RequestParam(value = "group", + required = false) final List groups) { + return processService.transitionWorkItem(process, id, taskId, phase, SecurityPolicy.of(user, groups), null) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + } + + @GetMapping(value = "$taskName$/schema", produces = MediaType.APPLICATION_JSON_VALUE) + public Map getSchema() { + return JsonSchemaUtil.load(this.getClass().getClassLoader(), process.id(), "$taskName$"); + } + + @GetMapping(value = "/{id}/$taskName$/{taskId}/schema", produces = MediaType.APPLICATION_JSON_VALUE) + public Map getSchemaAndPhases(@PathVariable("id") final String id, + @PathVariable("taskId") final String taskId, + @RequestParam(value = "user", required = false) final String user, + @RequestParam(value = "group", + required = false) final List groups) { + return processService.getWorkItemSchemaAndPhases(process, id, taskId, "$taskName$", SecurityPolicy.of(user, groups)); + } + +} \ No newline at end of file diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/RestResourceUserTaskQuarkusTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/RestResourceUserTaskQuarkusTemplate.java new file mode 100644 index 00000000000..231dfcc737e --- /dev/null +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/RestResourceUserTaskQuarkusTemplate.java @@ -0,0 +1,248 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.myspace.demo; + +import java.util.Map; +import java.util.List; +import java.util.Collection; + +import jakarta.ws.rs.NotFoundException; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriInfo; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.Consumes; + +import org.kie.kogito.auth.IdentityProviders; +import org.kie.kogito.auth.SecurityPolicy; +import org.kie.kogito.process.Process; +import org.kie.kogito.process.ProcessInstance; +import org.kie.kogito.process.ProcessInstanceReadMode; +import org.kie.kogito.process.WorkItem; +import org.kie.kogito.process.impl.Sig; +import org.kie.kogito.services.uow.UnitOfWorkExecutor; +import org.kie.kogito.usertask.UserTaskService; +import org.kie.kogito.usertask.view.UserTaskView; +import org.kie.kogito.usertask.view.UserTaskTransitionView; + +import org.kie.kogito.usertask.model.*; + +import jakarta.inject.Inject; + +@Path("/usertasks/instance") +public class UserTasksResource { + + @Inject + UserTaskService userTaskService; + + @GET + @Produces(MediaType.APPLICATION_JSON) + public List list(@QueryParam("user") String user, @QueryParam("group") List groups) { + return userTaskService.list(IdentityProviders.of(user, groups)); + } + + @GET + @Path("/{taskId}") + @Produces(MediaType.APPLICATION_JSON) + public UserTaskView find(@PathParam("taskId") String taskId, @QueryParam("user") String user, @QueryParam("group") List groups) { + return userTaskService.getUserTaskInstance(taskId, IdentityProviders.of(user, groups)).orElseThrow(NotFoundException::new); + } + + @POST + @Path("/{taskId}/transition") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public UserTaskView transition( + @PathParam("taskId") String taskId, + @QueryParam("transitionId") String transitionId, + @QueryParam("user") String user, + @QueryParam("group") List groups, Map data) { + return userTaskService.transition(taskId, transitionId, data, IdentityProviders.of(user, groups)).orElseThrow(NotFoundException::new); + } + + @GET + @Path("/{taskId}/transition") + @Produces(MediaType.APPLICATION_JSON) + public Collection transition( + @PathParam("taskId") String taskId, + @QueryParam("user") String user, + @QueryParam("group") List groups) { + return userTaskService.allowedTransitions(taskId, IdentityProviders.of(user, groups)); + } + + @PUT + @Path("/{taskId}/outputs") + @Consumes(MediaType.APPLICATION_JSON) + public UserTaskView setOutput( + @PathParam("taskId") String taskId, + @QueryParam("user") String user, + @QueryParam("group") List groups, + Map data) { + return userTaskService.setOutputs(taskId, data, IdentityProviders.of(user, groups)).orElseThrow(NotFoundException::new); + } + + @PUT + @Path("/{taskId}/inputs") + @Consumes(MediaType.APPLICATION_JSON) + public UserTaskView setOutput(@PathParam("id") String id, + @PathParam("taskId") String taskId, + @QueryParam("user") String user, + @QueryParam("group") List groups, + Map data) { + return userTaskService.setInputs(taskId, data, IdentityProviders.of(user, groups)).orElseThrow(NotFoundException::new); + } + + @GET + @Path("/{taskId}/comments") + @Produces(MediaType.APPLICATION_JSON) + public Collection getComments( + @PathParam("taskId") String taskId, + @QueryParam("user") String user, + @QueryParam("group") List groups) { + return userTaskService.getComments(taskId, IdentityProviders.of(user, groups)); + } + + @POST + @Path("/{taskId}/comments") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Comment addComment( + @PathParam("taskId") String taskId, + @QueryParam("user") String user, + @QueryParam("group") List groups, + CommentInfo commentInfo) { + Comment comment = new Comment(null, user); + comment.setContent(commentInfo.getComment()); + return userTaskService.addComment(taskId, comment, IdentityProviders.of(user, groups)).orElseThrow(NotFoundException::new); + } + + @GET + @Path("/{taskId}/comments/{commentId}") + @Produces(MediaType.APPLICATION_JSON) + public Comment getComment( + @PathParam("taskId") String taskId, + @PathParam("commentId") String commentId, + @QueryParam("user") String user, + @QueryParam("group") List groups) { + return userTaskService.getComment(taskId, commentId, IdentityProviders.of(user, groups)) + .orElseThrow(() -> new NotFoundException("Comment " + commentId + " not found")); + } + + @PUT + @Path("/{taskId}/comments/{commentId}") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Comment updateComment( + @PathParam("taskId") String taskId, + @PathParam("commentId") String commentId, + @QueryParam("user") String user, + @QueryParam("group") List groups, + CommentInfo commentInfo) { + Comment comment = new Comment(commentId, user); + comment.setContent(commentInfo.getComment()); + return userTaskService.updateComment(taskId, comment, IdentityProviders.of(user, groups)) + .orElseThrow(NotFoundException::new); + } + + @DELETE + @Path("/{taskId}/comments/{commentId}") + public Comment deleteComment( + @PathParam("taskId") String taskId, + @PathParam("commentId") String commentId, + @QueryParam("user") String user, + @QueryParam("group") List groups) { + return userTaskService.removeComment(taskId, commentId, IdentityProviders.of(user, groups)) + .orElseThrow(NotFoundException::new); + } + + @GET + @Path("/{taskId}/attachments") + @Produces(MediaType.APPLICATION_JSON) + public Collection getAttachments( + @PathParam("taskId") String taskId, + @QueryParam("user") String user, + @QueryParam("group") List groups) { + return userTaskService.getAttachments(taskId, IdentityProviders.of(user, groups)); + } + + @POST + @Path("/{taskId}/attachments") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Attachment addAttachment( + @PathParam("taskId") String taskId, + @QueryParam("user") String user, + @QueryParam("group") List groups, + AttachmentInfo attachmentInfo) { + Attachment attachment = new Attachment(null, user); + attachment.setName(attachmentInfo.getName()); + attachment.setContent(attachmentInfo.getUri()); + return userTaskService.addAttachment(taskId, attachment, IdentityProviders.of(user, groups)) + .orElseThrow(NotFoundException::new); + } + + @PUT + @Path("/{taskId}/attachments/{attachmentId}") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Attachment updateAttachment( + @PathParam("taskId") String taskId, + @PathParam("attachmentId") String attachmentId, + @QueryParam("user") String user, + @QueryParam("group") List groups, + AttachmentInfo attachmentInfo) { + Attachment attachment = new Attachment(attachmentId, user); + attachment.setName(attachmentInfo.getName()); + attachment.setContent(attachmentInfo.getUri()); + return userTaskService.updateAttachment(taskId, attachment, IdentityProviders.of(user, groups)) + .orElseThrow(NotFoundException::new); + } + + @DELETE + @Path("/{taskId}/attachments/{attachmentId}") + public Attachment deleteAttachment( + @PathParam("taskId") String taskId, + @PathParam("attachmentId") String attachmentId, + @QueryParam("user") String user, + @QueryParam("group") List groups) { + return userTaskService.removeAttachment(taskId, attachmentId, IdentityProviders.of(user, groups)) + .orElseThrow(NotFoundException::new); + } + + @GET + @Path("/{taskId}/attachments/{attachmentId}") + @Produces(MediaType.APPLICATION_JSON) + public Attachment getAttachment( + @PathParam("taskId") String taskId, + @PathParam("attachmentId") String attachmentId, + @QueryParam("user") String user, + @QueryParam("group") List groups) { + return userTaskService.getAttachment(taskId, attachmentId, IdentityProviders.of(user, groups)) + .orElseThrow(() -> new NotFoundException("Attachment " + attachmentId + " not found")); + } + +} diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/RestResourceUserTaskSpringTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/RestResourceUserTaskSpringTemplate.java new file mode 100644 index 00000000000..87c757d3d79 --- /dev/null +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/RestResourceUserTaskSpringTemplate.java @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.myspace.demo; + +import java.util.List; +import java.util.Map; +import java.util.Collection; + +import org.jbpm.util.JsonSchemaUtil; +import org.kie.kogito.auth.IdentityProviders; +import org.kie.kogito.auth.SecurityPolicy; +import org.kie.kogito.process.ProcessInstance; +import org.kie.kogito.process.WorkItem; +import org.kie.kogito.process.impl.Sig; +import org.kie.kogito.services.uow.UnitOfWorkExecutor; +import org.kie.kogito.usertask.UserTaskService; +import org.kie.kogito.usertask.view.UserTaskTransitionView; +import org.kie.kogito.usertask.view.UserTaskView; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.server.ResponseStatusException; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.beans.factory.annotation.Autowired; + +import org.kie.kogito.usertask.model.*; + +@RestController +@RequestMapping(value = "/usertasks/instance", produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE) +public class UserTasksResource { + + @Autowired + UserTaskService userTaskService; + + @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) + public List list(@RequestParam("user") String user, @RequestParam("group") List groups) { + return userTaskService.list(IdentityProviders.of(user, groups)); + } + + @GetMapping(value = "/{taskId}", produces = MediaType.APPLICATION_JSON_VALUE) + public UserTaskView find(@PathVariable("taskId") String taskId, @RequestParam("user") String user, @RequestParam("group") List groups) { + return userTaskService.getUserTaskInstance(taskId, IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + } + + @PostMapping(value = "/{taskId}/transition") + public UserTaskView transition( + @PathVariable("taskId") String taskId, + @RequestParam("transitionId") String transitionId, + @RequestParam("user") String user, + @RequestParam("group") List groups, + @RequestBody Map data) { + return userTaskService.transition(taskId, transitionId, data, IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + } + + @GetMapping(value = "/{taskId}/transition", produces = MediaType.APPLICATION_JSON_VALUE) + public Collection transition( + @PathVariable("taskId") String taskId, + @RequestParam("user") String user, + @RequestParam("group") List groups) { + return userTaskService.allowedTransitions(taskId, IdentityProviders.of(user, groups)); + } + + @PutMapping("/{taskId}/outputs") + public UserTaskView setOutput( + @PathVariable("taskId") String taskId, + @RequestParam("user") String user, + @RequestParam("group") List groups, + @RequestBody Map data) { + return userTaskService.setOutputs(taskId, data, IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + } + + @PutMapping("/{taskId}/inputs") + public UserTaskView setOutput(@PathVariable("id") String id, + @PathVariable("taskId") String taskId, + @RequestParam("user") String user, + @RequestParam("group") List groups, + @RequestBody Map data) { + return userTaskService.setInputs(taskId, data, IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + } + + @GetMapping("/{taskId}/comments") + public Collection getComments( + @PathVariable("taskId") String taskId, + @RequestParam("user") String user, + @RequestParam("group") List groups) { + return userTaskService.getComments(taskId, IdentityProviders.of(user, groups)); + } + + @PostMapping("/{taskId}/comments") + public Comment addComment( + @PathVariable("taskId") String taskId, + @RequestParam("user") String user, + @RequestParam("group") List groups, + @RequestBody CommentInfo commentInfo) { + Comment comment = new Comment(null, user); + comment.setContent(commentInfo.getComment()); + return userTaskService.addComment(taskId, comment, IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + } + + @GetMapping("/{taskId}/comments/{commentId}") + public Comment getComment( + @PathVariable("taskId") String taskId, + @PathVariable("commentId") String commentId, + @RequestParam("user") String user, + @RequestParam("group") List groups) { + return userTaskService.getComment(taskId, commentId, IdentityProviders.of(user, groups)) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Comment " + commentId + " not found")); + } + + @PutMapping("/{taskId}/comments/{commentId}") + public Comment updateComment( + @PathVariable("taskId") String taskId, + @PathVariable("commentId") String commentId, + @RequestParam("user") String user, + @RequestParam("group") List groups, + @RequestBody CommentInfo commentInfo) { + Comment comment = new Comment(commentId, user); + comment.setContent(commentInfo.getComment()); + return userTaskService.updateComment(taskId, comment, IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + } + + @DeleteMapping("/{taskId}/comments/{commentId}") + public Comment deleteComment( + @PathVariable("taskId") String taskId, + @PathVariable("commentId") String commentId, + @RequestParam("user") String user, + @RequestParam("group") List groups) { + return userTaskService.removeComment(taskId, commentId, IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + } + + @GetMapping("/{taskId}/attachments") + public Collection getAttachments( + @PathVariable("taskId") String taskId, + @RequestParam("user") String user, + @RequestParam("group") List groups) { + return userTaskService.getAttachments(taskId, IdentityProviders.of(user, groups)); + } + + @PostMapping("/{taskId}/attachments") + public Attachment addAttachment( + @PathVariable("taskId") String taskId, + @RequestParam("user") String user, + @RequestParam("group") List groups, + @RequestBody AttachmentInfo attachmentInfo) { + Attachment attachment = new Attachment(null, user); + attachment.setName(attachmentInfo.getName()); + attachment.setContent(attachmentInfo.getUri()); + return userTaskService.addAttachment(taskId, attachment, IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + } + + @PutMapping("/{taskId}/attachments/{attachmentId}") + public Attachment updateAttachment( + @PathVariable("taskId") String taskId, + @PathVariable("attachmentId") String attachmentId, + @RequestParam("user") String user, + @RequestParam("group") List groups, + @RequestBody AttachmentInfo attachmentInfo) { + Attachment attachment = new Attachment(attachmentId, user); + attachment.setName(attachmentInfo.getName()); + attachment.setContent(attachmentInfo.getUri()); + return userTaskService.updateAttachment(taskId, attachment, IdentityProviders.of(user, groups)) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + } + + @DeleteMapping("/{taskId}/attachments/{attachmentId}") + public Attachment deleteAttachment( + @PathVariable("taskId") String taskId, + @PathVariable("attachmentId") String attachmentId, + @RequestParam("user") String user, + @RequestParam("group") List groups) { + return userTaskService.removeAttachment(taskId, attachmentId, IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + } + + @GetMapping("/{taskId}/attachments/{attachmentId}") + public Attachment getAttachment( + @PathVariable("taskId") String taskId, + @PathVariable("attachmentId") String attachmentId, + @RequestParam("user") String user, + @RequestParam("group") List groups) { + return userTaskService.getAttachment(taskId, attachmentId, IdentityProviders.of(user, groups)) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Attachment " + attachmentId + " not found")); + } + +} \ No newline at end of file diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskConfigQuarkusTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskConfigQuarkusTemplate.java index 3486fad3843..3d327fb03f8 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskConfigQuarkusTemplate.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskConfigQuarkusTemplate.java @@ -1,3 +1,4 @@ + /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -16,6 +17,8 @@ * specific language governing permissions and limitations * under the License. */ +import java.util.List; + import org.kie.api.event.process.ProcessEventListener; import org.kie.kogito.auth.IdentityProvider; import org.kie.kogito.event.EventPublisher; @@ -27,26 +30,31 @@ import org.kie.kogito.uow.events.UnitOfWorkEventListener; import org.kie.kogito.usertask.impl.DefaultUserTaskConfig; import org.kie.kogito.usertask.lifecycle.UserTaskLifeCycle; +import org.kie.kogito.usertask.UserTaskAssignmentStrategyConfig; import org.kie.kogito.usertask.UserTaskEventListenerConfig; +import org.kie.kogito.usertask.UserTaskInstances; import jakarta.enterprise.inject.Instance; @jakarta.inject.Singleton public class UserTaskConfig extends DefaultUserTaskConfig { - - + @jakarta.inject.Inject public UserTaskConfig( Instance workItemHandlerConfig, Instance unitOfWorkManager, Instance jobsService, Instance identityProvider, - Instance userTaskLifeCycle) { - super(workItemHandlerConfig, + Instance userTaskLifeCycle, + Instance userTaskAssignmentStrategyConfigs, + Instance userTaskInstances) { + super(workItemHandlerConfig, unitOfWorkManager, jobsService, identityProvider, - userTaskLifeCycle); + userTaskLifeCycle, + userTaskAssignmentStrategyConfigs, + userTaskInstances); } - + } \ No newline at end of file diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskConfigSpringTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskConfigSpringTemplate.java index d8e00dbc576..11f7935f436 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskConfigSpringTemplate.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskConfigSpringTemplate.java @@ -1,3 +1,4 @@ + /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -29,24 +30,30 @@ import org.kie.kogito.uow.events.UnitOfWorkEventListener; import org.kie.kogito.usertask.impl.DefaultUserTaskConfig; import org.kie.kogito.usertask.lifecycle.UserTaskLifeCycle; +import org.kie.kogito.usertask.UserTaskAssignmentStrategyConfig; import org.kie.kogito.usertask.UserTaskEventListenerConfig; +import org.kie.kogito.usertask.UserTaskInstances; @org.springframework.stereotype.Component public class UserTaskConfig extends DefaultUserTaskConfig { - + @org.springframework.beans.factory.annotation.Autowired public UserTaskConfig( List workItemHandlerConfig, List unitOfWorkManager, List jobsService, List identityProvider, - List userTaskLifeCycle) { + List userTaskLifeCycle, + List userTaskAssignmentStrategyConfigs, + List userTaskInstances) { - super(workItemHandlerConfig, + super(workItemHandlerConfig, unitOfWorkManager, jobsService, identityProvider, - userTaskLifeCycle); + userTaskLifeCycle, + userTaskAssignmentStrategyConfigs, + userTaskInstances); } - + } \ No newline at end of file diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskQuarkusTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskQuarkusTemplate.java index 5e915790f91..3d5213b17a7 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskQuarkusTemplate.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskQuarkusTemplate.java @@ -22,6 +22,9 @@ import org.kie.kogito.Application; import org.kie.kogito.usertask.UserTask; +import org.kie.kogito.usertask.UserTaskAssignmentStrategy; +import org.kie.kogito.usertask.UserTaskConfig; +import org.kie.kogito.usertask.UserTaskInstances; import org.kie.kogito.usertask.impl.DefaultUserTask; public class UserTaskTemplate extends DefaultUserTask { @@ -32,9 +35,16 @@ public class UserTaskTemplate extends DefaultUserTask { public UserTaskTemplate() { } + + @Override + public UserTaskAssignmentStrategy getAssignmentStrategy() { + UserTaskConfig userTaskConfig = application.config().get(UserTaskConfig.class); + return userTaskConfig.userTaskAssignmentStrategies().defaultUserTaskAssignmentStrategy(); + } - @jakarta.annotation.PostConstruct - public void setup() { - this.setApplication(application); + @Override + public UserTaskInstances instances() { + UserTaskConfig userTaskConfig = application.config().get(UserTaskConfig.class); + return userTaskConfig.userTaskInstances(); } } diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskSpringTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskSpringTemplate.java index 64e9aa53542..eae587b8eab 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskSpringTemplate.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTaskSpringTemplate.java @@ -22,6 +22,9 @@ import org.kie.kogito.Application; import org.kie.kogito.usertask.UserTask; +import org.kie.kogito.usertask.UserTaskAssignmentStrategy; +import org.kie.kogito.usertask.UserTaskConfig; +import org.kie.kogito.usertask.UserTaskInstances; import org.kie.kogito.usertask.impl.DefaultUserTask; public class UserTaskTemplate extends DefaultUserTask { @@ -33,8 +36,16 @@ public UserTaskTemplate() { } - @jakarta.annotation.PostConstruct - public void setup() { - this.setApplication(application); + @Override + public UserTaskAssignmentStrategy getAssignmentStrategy() { + UserTaskConfig userTaskConfig = application.config().get(UserTaskConfig.class); + return userTaskConfig.userTaskAssignmentStrategies().defaultUserTaskAssignmentStrategy(); } + + @Override + public UserTaskInstances instances() { + UserTaskConfig userTaskConfig = application.config().get(UserTaskConfig.class); + return userTaskConfig.userTaskInstances(); + } + } diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerQuarkusTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerQuarkusTemplate.java index 9d22ea14eee..8e98179784d 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerQuarkusTemplate.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerQuarkusTemplate.java @@ -22,21 +22,40 @@ import java.util.HashMap; import java.util.Map; import java.util.List; + +import org.kie.kogito.uow.events.UnitOfWorkUserTaskEventListener; +import org.kie.kogito.usertask.impl.DefaultUserTaskInstance; +import org.kie.kogito.usertask.impl.KogitoUserTaskEventSupportImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.kie.kogito.usertask.UserTask; +import org.kie.kogito.usertask.UserTaskConfig; +import org.kie.kogito.usertask.UserTaskInstance; +import org.kie.kogito.usertask.UserTaskInstances; + import jakarta.enterprise.inject.Instance; @jakarta.enterprise.context.ApplicationScoped public class UserTasks implements org.kie.kogito.usertask.UserTasks { + private static Logger LOG = LoggerFactory.getLogger(UserTasks.class); + @jakarta.inject.Inject jakarta.enterprise.inject.Instance userTasks; + @jakarta.inject.Inject + Application application; + private Map mappedUserTask = new HashMap<>(); @jakarta.annotation.PostConstruct public void setup() { + UserTaskInstances userTaskInstances = application.config().get(UserTaskConfig.class).userTaskInstances(); + userTaskInstances.setDisconnectUserTaskInstance(this::disconnect); + userTaskInstances.setReconnectUserTaskInstance(this::connect); for (UserTask userTask : userTasks) { mappedUserTask.put(userTask.id(), userTask); + LOG.info("Registering user task {} with task name {}", userTask.id(), userTask.getTaskName()); } } @@ -47,4 +66,31 @@ public UserTask userTaskById(String userTaskId) { public Collection userTaskIds() { return mappedUserTask.keySet(); } + + @Override + public UserTaskInstances instances() { + return application.config().get(UserTaskConfig.class).userTaskInstances(); + } + + private UserTaskInstance disconnect(UserTaskInstance userTaskInstance) { + DefaultUserTaskInstance instance = (DefaultUserTaskInstance) userTaskInstance; + instance.setUserTask(null); + instance.setUserTaskEventSupport(null); + instance.setUserTaskLifeCycle(null); + instance.setInstances(null); + return instance; + } + + public UserTaskInstance connect(UserTaskInstance userTaskInstance) { + DefaultUserTaskInstance instance = (DefaultUserTaskInstance) userTaskInstance; + UserTaskConfig userTaskConfig = application.config().get(UserTaskConfig.class); + KogitoUserTaskEventSupportImpl impl = new KogitoUserTaskEventSupportImpl(userTaskConfig.identityProvider()); + userTaskConfig.userTaskEventListeners().listeners().forEach(impl::addEventListener); + impl.addEventListener(new UnitOfWorkUserTaskEventListener(application.unitOfWorkManager())); + instance.setUserTask(application.get(UserTasks.class).userTaskById(instance.getUserTaskId())); + instance.setUserTaskEventSupport(impl); + instance.setUserTaskLifeCycle(userTaskConfig.userTaskLifeCycle()); + instance.setInstances(application.config().get(UserTaskConfig.class).userTaskInstances()); + return instance; + } } \ No newline at end of file diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerSpringTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerSpringTemplate.java index 1e65367bfc5..9785b44d7c4 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerSpringTemplate.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksContainerSpringTemplate.java @@ -22,24 +22,44 @@ import java.util.HashMap; import java.util.Map; import java.util.List; + +import org.kie.kogito.Application; +import org.kie.kogito.uow.events.UnitOfWorkUserTaskEventListener; +import org.kie.kogito.usertask.impl.DefaultUserTaskInstance; +import org.kie.kogito.usertask.impl.KogitoUserTaskEventSupportImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.kie.kogito.usertask.UserTask; +import org.kie.kogito.usertask.UserTaskConfig; +import org.kie.kogito.usertask.UserTaskInstance; +import org.kie.kogito.usertask.UserTaskInstances; @org.springframework.web.context.annotation.ApplicationScope @org.springframework.stereotype.Component public class UserTasks implements org.kie.kogito.usertask.UserTasks { - + + private static Logger LOG = LoggerFactory.getLogger(UserTasks.class); + @org.springframework.beans.factory.annotation.Autowired Collection userTasks; + @org.springframework.beans.factory.annotation.Autowired + Application application; + private Map mappedUserTask = new HashMap<>(); - + @jakarta.annotation.PostConstruct public void setup() { + UserTaskInstances userTaskInstances = application.config().get(UserTaskConfig.class).userTaskInstances(); + userTaskInstances.setDisconnectUserTaskInstance(this::disconnect); + userTaskInstances.setReconnectUserTaskInstance(this::connect); + for (UserTask userTask : userTasks) { mappedUserTask.put(userTask.id(), userTask); + LOG.info("Registering user task {} with task name {}", userTask.id(), userTask.getTaskName()); } } - + public UserTask userTaskById(String userTaskId) { return mappedUserTask.get(userTaskId); } @@ -47,4 +67,31 @@ public UserTask userTaskById(String userTaskId) { public Collection userTaskIds() { return mappedUserTask.keySet(); } + + @Override + public UserTaskInstances instances() { + return application.config().get(UserTaskConfig.class).userTaskInstances(); + } + + private UserTaskInstance disconnect(UserTaskInstance userTaskInstance) { + DefaultUserTaskInstance instance = (DefaultUserTaskInstance) userTaskInstance; + instance.setUserTask(null); + instance.setUserTaskEventSupport(null); + instance.setUserTaskLifeCycle(null); + instance.setInstances(null); + return instance; + } + + public UserTaskInstance connect(UserTaskInstance userTaskInstance) { + DefaultUserTaskInstance instance = (DefaultUserTaskInstance) userTaskInstance; + UserTaskConfig userTaskConfig = application.config().get(UserTaskConfig.class); + KogitoUserTaskEventSupportImpl impl = new KogitoUserTaskEventSupportImpl(userTaskConfig.identityProvider()); + userTaskConfig.userTaskEventListeners().listeners().forEach(impl::addEventListener); + impl.addEventListener(new UnitOfWorkUserTaskEventListener(application.unitOfWorkManager())); + instance.setUserTask(application.get(org.kie.kogito.usertask.UserTasks.class).userTaskById(instance.getUserTaskId())); + instance.setUserTaskEventSupport(impl); + instance.setUserTaskLifeCycle(userTaskConfig.userTaskLifeCycle()); + instance.setInstances(application.config().get(UserTaskConfig.class).userTaskInstances()); + return instance; + } } \ No newline at end of file diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksServiceProducerQuarkusTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksServiceProducerQuarkusTemplate.java new file mode 100644 index 00000000000..837b7312e1f --- /dev/null +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksServiceProducerQuarkusTemplate.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package $Package$; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.Instance; + +import org.kie.kogito.Application; +import org.kie.kogito.internal.process.workitem.KogitoWorkItemHandler; +import org.kie.kogito.jbpm.usertask.handler.UserTaskKogitoWorkItemHandler; +import org.kie.kogito.jbpm.usertask.handler.UserTaskKogitoWorkItemHandlerProcessListener; +import org.kie.kogito.process.Processes; +import org.kie.kogito.usertask.UserTask; +import org.kie.kogito.usertask.UserTaskAssignmentStrategy; +import org.kie.kogito.usertask.UserTaskAssignmentStrategyConfig; +import org.kie.kogito.usertask.UserTaskEventListener; +import org.kie.kogito.usertask.UserTaskService; +import org.kie.kogito.usertask.UserTaskConfig; +import org.kie.kogito.usertask.UserTaskEventListenerConfig; +import org.kie.kogito.usertask.impl.DefaultUserTaskEventListenerConfig; +import org.kie.kogito.usertask.impl.DefaultUserTaskAssignmentStrategyConfig; +import org.kie.kogito.usertask.impl.UserTaskServiceImpl; + +@ApplicationScoped +public class UserTasksServiceProducer { + + @Produces + public UserTaskService userTaskService(Application application) { + return new UserTaskServiceImpl(application); + } + + @Produces + public UserTaskEventListener userTaskListener(Processes processes) { + return new UserTaskKogitoWorkItemHandlerProcessListener(processes); + } + + @Produces + public KogitoWorkItemHandler userTaskWorkItemHandler(Application application) { + return new UserTaskKogitoWorkItemHandler(application); + } + + @Produces + public UserTaskEventListenerConfig userTaskEventListenerConfig(Instance userTaskEventListener) { + return new DefaultUserTaskEventListenerConfig(userTaskEventListener); + } + + @Produces + public UserTaskAssignmentStrategyConfig userTaskAssignmentStrategyConfig(Instance userTaskAssignmentStrategy) { + return new DefaultUserTaskAssignmentStrategyConfig(userTaskAssignmentStrategy); + } +} diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksServiceProducerSpringTemplate.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksServiceProducerSpringTemplate.java new file mode 100644 index 00000000000..088d7ad1938 --- /dev/null +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/resources/class-templates/usertask/UserTasksServiceProducerSpringTemplate.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package $Package$; + +import java.util.List; +import org.kie.kogito.Application; +import org.kie.kogito.internal.process.workitem.KogitoWorkItemHandler; +import org.kie.kogito.jbpm.usertask.handler.UserTaskKogitoWorkItemHandler; +import org.kie.kogito.jbpm.usertask.handler.UserTaskKogitoWorkItemHandlerProcessListener; +import org.kie.kogito.process.ProcessService; +import org.kie.kogito.process.Processes; +import org.kie.kogito.process.impl.ProcessServiceImpl; +import org.kie.kogito.usertask.UserTaskEventListenerConfig; +import org.kie.kogito.usertask.UserTaskAssignmentStrategyConfig; +import org.kie.kogito.usertask.UserTaskAssignmentStrategy; +import org.kie.kogito.usertask.UserTaskEventListener; +import org.kie.kogito.usertask.impl.DefaultUserTaskEventListenerConfig; +import org.kie.kogito.usertask.impl.DefaultUserTaskAssignmentStrategyConfig; +import org.kie.kogito.usertask.impl.UserTaskServiceImpl; +import org.kie.kogito.usertask.UserTaskService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class UserTaskServiceProducer { + + @Bean + public UserTaskService userTaskService(Application application) { + return new UserTaskServiceImpl(application); + } + + @Bean + public UserTaskEventListener userTaskListener(Processes processes) { + return new UserTaskKogitoWorkItemHandlerProcessListener(processes); + } + + @Bean + public KogitoWorkItemHandler userTaskWorkItemHandler(Application application) { + return new UserTaskKogitoWorkItemHandler(application); + } + + @Bean + public UserTaskEventListenerConfig userTaskEventListenerConfig(List userTaskEventListener) { + return new DefaultUserTaskEventListenerConfig(userTaskEventListener); + } + + @Bean + public UserTaskAssignmentStrategyConfig userTaskAssignmentStrategyConfig(List userTaskAssignmentStrategy) { + return new DefaultUserTaskAssignmentStrategyConfig(userTaskAssignmentStrategy); + } +} diff --git a/quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-processes-persistence-common/src/test/java/org/kie/kogito/it/PersistenceTest.java b/quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-processes-persistence-common/src/test/java/org/kie/kogito/it/PersistenceTest.java index 72a8a4a6fc5..c0d75bce397 100644 --- a/quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-processes-persistence-common/src/test/java/org/kie/kogito/it/PersistenceTest.java +++ b/quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-processes-persistence-common/src/test/java/org/kie/kogito/it/PersistenceTest.java @@ -303,7 +303,7 @@ void testAdHocProcess() { .queryParam("user", "user") .queryParam("group", "agroup") .when() - .post("/AdHocProcess/{pId}/CloseTask") + .post("/AdHocProcess/{pId}/CloseTask/trigger") .then() .log().everything() .statusCode(201) diff --git a/quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-quarkus-processes-infinispan/src/main/resources/AddedTask.bpmn2 b/quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-quarkus-processes-infinispan/src/main/resources/AddedTask.bpmn2 new file mode 100644 index 00000000000..6d8f8618cd1 --- /dev/null +++ b/quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-quarkus-processes-infinispan/src/main/resources/AddedTask.bpmn2 @@ -0,0 +1,231 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _D0E6CA53-1ECE-4F8D-AAF0-829B30A2719F + _2DFFEEA8-CA22-4076-A703-DB36A6471775 + + + + + + + + + _916690AE-8B09-4FA5-BF0D-8638E01A7472 + _D0E6CA53-1ECE-4F8D-AAF0-829B30A2719F + + + + + _74EA6719-7F99-48CD-A98E-17D422668B87_TaskNameInputX + _74EA6719-7F99-48CD-A98E-17D422668B87_SkippableInputX + + + + _74EA6719-7F99-48CD-A98E-17D422668B87_TaskNameInputX + + + + + + + _74EA6719-7F99-48CD-A98E-17D422668B87_SkippableInputX + + + + + + + + mary + + + + + _FA3E1FF8-4894-47DA-90EB-DAB27BD7E925 + + + + + + + + _2DFFEEA8-CA22-4076-A703-DB36A6471775 + _FA3E1FF8-4894-47DA-90EB-DAB27BD7E925 + System.out.println("Ending process " + kcontext.getProcessInstance().getId() + " [" + kcontext.getProcessInstance().getProcessName() + ", v" + kcontext.getProcessInstance().getProcessVersion() + "]"); + + + + + + + + _C7A1A566-057F-4A80-B654-61270F82F365 + _916690AE-8B09-4FA5-BF0D-8638E01A7472 + System.out.println("Started process " + kcontext.getProcessInstance().getId() + " [" + kcontext.getProcessInstance().getProcessName() + ", v" + kcontext.getProcessInstance().getProcessVersion() + "]"); + + + _C7A1A566-057F-4A80-B654-61270F82F365 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _ENJmkGP9ED2iIdGE-n8pYg + _ENJmkGP9ED2iIdGE-n8pYg + + \ No newline at end of file diff --git a/quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-quarkus-processes-infinispan/src/test/java/org/kie/kogito/it/InfinispanPersistenceIT.java b/quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-quarkus-processes-infinispan/src/test/java/org/kie/kogito/it/InfinispanPersistenceIT.java index fd779ca5759..cc668b06431 100644 --- a/quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-quarkus-processes-infinispan/src/test/java/org/kie/kogito/it/InfinispanPersistenceIT.java +++ b/quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-quarkus-processes-infinispan/src/test/java/org/kie/kogito/it/InfinispanPersistenceIT.java @@ -18,9 +18,84 @@ */ package org.kie.kogito.it; +import java.util.Collections; + +import org.junit.jupiter.api.Test; + import io.quarkus.test.junit.QuarkusIntegrationTest; +import io.restassured.http.ContentType; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; @QuarkusIntegrationTest class InfinispanPersistenceIT extends PersistenceTest { + private static final String USER_TASK_BASE_PATH = "/usertasks/instance"; + + @Test + public void testStartApprovalAuthorized() { + // start new approval + String id = given() + .body("{}") + .contentType(ContentType.JSON) + .when() + .post("/AddedTask") + .then() + .statusCode(201) + .body("id", notNullValue()).extract().path("id"); + // get all active approvals + given() + .accept(ContentType.JSON) + .when() + .get("/AddedTask") + .then() + .statusCode(200) + .body("size()", is(1), "[0].id", is(id)); + + // get just started approval + given() + .accept(ContentType.JSON) + .when() + .get("/AddedTask/" + id) + .then() + .statusCode(200) + .body("id", is(id)); + + // tasks assigned in just started approval + + String userTaskId = given() + .basePath(USER_TASK_BASE_PATH) + .queryParam("user", "mary") + .queryParam("group", "managers") + .contentType(ContentType.JSON) + .when() + .get() + .then() + .statusCode(200) + .extract() + .body() + .path("[0].id"); + + given() + .contentType(ContentType.JSON) + .basePath(USER_TASK_BASE_PATH) + .queryParam("transitionId", "complete") + .queryParam("user", "mary") + .queryParam("group", "managers") + .body(Collections.emptyMap()) + .when() + .post("/{userTaskId}/transition", userTaskId) + .then() + .statusCode(200); + // get all active approvals + given() + .accept(ContentType.JSON) + .when() + .get("/AddedTask") + .then() + .statusCode(200) + .body("size()", is(1)); + } } diff --git a/quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/AdHocFragmentsIT.java b/quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/AdHocFragmentsIT.java index 083b39bec01..e0007fe8532 100644 --- a/quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/AdHocFragmentsIT.java +++ b/quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/AdHocFragmentsIT.java @@ -64,7 +64,7 @@ void testUserTaskProcess() { .queryParam("user", "john") .queryParam("group", "manager") .when() - .post("/AdHocFragments/{pid}/AdHocTask1", id) + .post("/AdHocFragments/{pid}/AdHocTask1/trigger", id) .then() .statusCode(201) .header("Location", not(emptyOrNullString())) diff --git a/quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/TaskIT.java b/quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/TaskIT.java index d5a5513a165..17cac753e50 100644 --- a/quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/TaskIT.java +++ b/quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/TaskIT.java @@ -20,22 +20,21 @@ import java.io.IOException; import java.io.InputStream; +import java.net.URI; import java.util.Collections; import java.util.Map; import java.util.Set; import org.acme.travels.Traveller; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.kie.kogito.task.management.service.TaskInfo; import org.kie.kogito.usertask.model.AttachmentInfo; +import org.kie.kogito.usertask.model.CommentInfo; import io.quarkus.test.junit.QuarkusIntegrationTest; import io.restassured.RestAssured; import io.restassured.http.ContentType; -import jakarta.ws.rs.core.UriBuilder; - import static io.restassured.RestAssured.given; import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchema; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -160,11 +159,10 @@ void testSaveTask() { } @Test - @Disabled("Revisited after ht endpoints") void testCommentAndAttachment() { - Traveller traveller = new Traveller("pepe", "rubiales", "pepe.rubiales@gmail.com", "Spanish"); + Traveller traveller = new Traveller("pepe", "rubiales", "pepe.rubiales@gmail.com", "Spanish", null); - String processId = given() + given() .contentType(ContentType.JSON) .when() .body(Collections.singletonMap("traveller", traveller)) @@ -176,154 +174,131 @@ void testCommentAndAttachment() { String taskId = given() .contentType(ContentType.JSON) - .queryParam("user", "manager") + .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .when() - .get("/approvals/{processId}/tasks") + .get("/usertasks/instance") .then() .statusCode(200) .extract() .path("[0].id"); - final String commentId = given().contentType(ContentType.TEXT) + final String commentId = given().contentType(ContentType.JSON) .when() - .queryParam("user", "manager") + .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) - .body("We need to act") - .post("/approvals/{processId}/firstLineApproval/{taskId}/comments") + .body(new CommentInfo("We need to act")) + .post("/usertasks/instance/{taskId}/comments") .then() - .statusCode(201) + .statusCode(200) .extract() .path("id"); final String commentText = "We have done everything we can"; - given().contentType(ContentType.TEXT) + given().contentType(ContentType.JSON) .when() - .queryParam("user", "manager") + .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) .pathParam("commentId", commentId) - .body(commentText) - .put("/approvals/{processId}/firstLineApproval/{taskId}/comments/{commentId}") + .body(new CommentInfo(commentText)) + .put("/usertasks/instance/{taskId}/comments/{commentId}") .then() .statusCode(200); assertEquals(commentText, given().contentType(ContentType.JSON) .when() - .queryParam("user", "manager") + .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) .pathParam("commentId", commentId) - .get("/approvals/{processId}/firstLineApproval/{taskId}/comments/{commentId}") + .get("/usertasks/instance/{taskId}/comments/{commentId}") .then() .statusCode(200).extract().path("content")); given().contentType(ContentType.JSON) .when() - .queryParam("user", "manager") + .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) .pathParam("commentId", commentId) - .delete("/approvals/{processId}/firstLineApproval/{taskId}/comments/{commentId}") + .delete("/usertasks/instance/{taskId}/comments/{commentId}") .then() .statusCode(200); given().contentType(ContentType.JSON) .when() - .queryParam("user", "manager") - .queryParam("group", "managers") - .pathParam("processId", processId) - .pathParam("taskId", taskId) - .pathParam("commentId", commentId) - .get("/approvals/{processId}/firstLineApproval/{taskId}/comments/{commentId}") - .then() - .statusCode(404); - - given().contentType(ContentType.JSON) - .when() - .queryParam("user", "manager") + .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) .pathParam("commentId", commentId) - .delete("/approvals/{processId}/firstLineApproval/{taskId}/comments/{commentId}") + .get("/usertasks/instance/{taskId}/comments/{commentId}") .then() .statusCode(404); final String attachmentId = given().contentType(ContentType.JSON) .when() - .queryParam("user", "manager") + .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) - .body(new AttachmentInfo(UriBuilder.fromPath("pepito.txt").scheme("file").build(), "pepito.txt")) - .post("/approvals/{processId}/firstLineApproval/{taskId}/attachments") + .body(new AttachmentInfo(URI.create("pepito.txt"), "pepito.txt")) + .post("/usertasks/instance/{taskId}/attachments") .then() - .statusCode(201) + .statusCode(200) .extract() .path("id"); given().contentType(ContentType.JSON) .when() - .queryParam("user", "manager") + .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) .pathParam("attachmentId", attachmentId) - .body(new AttachmentInfo(UriBuilder.fromPath("/home/fulanito.txt").scheme("file").build())) - .put("/approvals/{processId}/firstLineApproval/{taskId}/attachments/{attachmentId}") + .body(new AttachmentInfo(URI.create("file:/home/fulanito.txt"))) + .put("/usertasks/instance/{taskId}/attachments/{attachmentId}") .then() .statusCode(200); given().contentType( ContentType.JSON) .when() - .queryParam("user", "manager") + .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) .pathParam("attachmentId", attachmentId) - .get("/approvals/{processId}/firstLineApproval/{taskId}/attachments/{attachmentId}") + .get("/usertasks/instance/{taskId}/attachments/{attachmentId}") .then() .statusCode(200).body("name", equalTo("fulanito.txt")).body("content", equalTo( "file:/home/fulanito.txt")); given().contentType(ContentType.JSON) .when() - .queryParam("user", "manager") + .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) .pathParam("attachmentId", attachmentId) - .delete("/approvals/{processId}/firstLineApproval/{taskId}/attachments/{attachmentId}") + .delete("/usertasks/instance/{taskId}/attachments/{attachmentId}") .then() .statusCode(200); given().contentType(ContentType.JSON) .when() - .queryParam("user", "manager") + .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) .pathParam("attachmentId", attachmentId) - .get("/approvals/{processId}/firstLineApproval/{taskId}/attachments/{attachmentId}") + .get("/usertasks/instance/{taskId}/attachments/{attachmentId}") .then() .statusCode(404); given().contentType(ContentType.JSON) .when() - .queryParam("user", "manager") + .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) .pathParam("attachmentId", attachmentId) - .delete("/approvals/{processId}/firstLineApproval/{taskId}/attachments/{attachmentId}") + .delete("/usertasks/instance/{taskId}/attachments/{attachmentId}") .then() .statusCode(404); } diff --git a/springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/AdHocFragmentsTest.java b/springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/AdHocFragmentsTest.java index cec3f11a37e..0fb04479a50 100644 --- a/springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/AdHocFragmentsTest.java +++ b/springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/AdHocFragmentsTest.java @@ -58,7 +58,7 @@ void testUserTaskProcess() { .queryParam("user", "john") .queryParam("group", "manager") .when() - .post("/AdHocFragments/{pid}/AdHocTask1", id) + .post("/AdHocFragments/{pid}/AdHocTask1/trigger", id) .then() .statusCode(201) .header("Location", not(emptyOrNullString())) diff --git a/springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/TaskTest.java b/springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/TaskTest.java index 970968e6497..5da1e8e6cdf 100644 --- a/springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/TaskTest.java +++ b/springboot/integration-tests/integration-tests-springboot-processes-it/src/test/java/org/kie/kogito/integrationtests/springboot/TaskTest.java @@ -31,11 +31,11 @@ import org.acme.travels.Address; import org.acme.travels.Traveller; import org.jbpm.util.JsonSchemaUtil; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.kie.kogito.task.management.service.TaskInfo; import org.kie.kogito.usertask.model.AttachmentInfo; +import org.kie.kogito.usertask.model.CommentInfo; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -98,7 +98,7 @@ void testJsonSchema() { @Test void testJsonSchemaFiles() { - long expectedJsonSchemas = 23; + long expectedJsonSchemas = 25; Path jsonDir = Paths.get("target", "classes").resolve(JsonSchemaUtil.getJsonDir()); try (Stream paths = Files.walk(jsonDir)) { long generatedJsonSchemas = paths @@ -111,11 +111,10 @@ void testJsonSchemaFiles() { } @Test - @Disabled("Revisited after ht endpoints") void testCommentAndAttachment() { Traveller traveller = new Traveller("pepe", "rubiales", "pepe.rubiales@gmail.com", "Spanish", null); - String processId = given() + given() .contentType(ContentType.JSON) .when() .body(Collections.singletonMap("traveller", traveller)) @@ -129,37 +128,34 @@ void testCommentAndAttachment() { .contentType(ContentType.JSON) .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .when() - .get("/approvals/{processId}/tasks") + .get("/usertasks/instance") .then() .statusCode(200) .extract() .path("[0].id"); - final String commentId = given().contentType(ContentType.TEXT) + final String commentId = given().contentType(ContentType.JSON) .when() .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) - .body("We need to act") - .post("/approvals/{processId}/firstLineApproval/{taskId}/comments") + .body(new CommentInfo("We need to act")) + .post("/usertasks/instance/{taskId}/comments") .then() - .statusCode(201) + .statusCode(200) .extract() .path("id"); final String commentText = "We have done everything we can"; - given().contentType(ContentType.TEXT) + given().contentType(ContentType.JSON) .when() .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) .pathParam("commentId", commentId) - .body(commentText) - .put("/approvals/{processId}/firstLineApproval/{taskId}/comments/{commentId}") + .body(new CommentInfo(commentText)) + .put("/usertasks/instance/{taskId}/comments/{commentId}") .then() .statusCode(200); @@ -167,10 +163,9 @@ void testCommentAndAttachment() { .when() .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) .pathParam("commentId", commentId) - .get("/approvals/{processId}/firstLineApproval/{taskId}/comments/{commentId}") + .get("/usertasks/instance/{taskId}/comments/{commentId}") .then() .statusCode(200).extract().path("content")); @@ -178,10 +173,9 @@ void testCommentAndAttachment() { .when() .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) .pathParam("commentId", commentId) - .delete("/approvals/{processId}/firstLineApproval/{taskId}/comments/{commentId}") + .delete("/usertasks/instance/{taskId}/comments/{commentId}") .then() .statusCode(200); @@ -189,10 +183,9 @@ void testCommentAndAttachment() { .when() .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) .pathParam("commentId", commentId) - .get("/approvals/{processId}/firstLineApproval/{taskId}/comments/{commentId}") + .get("/usertasks/instance/{taskId}/comments/{commentId}") .then() .statusCode(404); @@ -200,12 +193,11 @@ void testCommentAndAttachment() { .when() .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) .body(new AttachmentInfo(URI.create("pepito.txt"), "pepito.txt")) - .post("/approvals/{processId}/firstLineApproval/{taskId}/attachments") + .post("/usertasks/instance/{taskId}/attachments") .then() - .statusCode(201) + .statusCode(200) .extract() .path("id"); @@ -213,11 +205,10 @@ void testCommentAndAttachment() { .when() .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) .pathParam("attachmentId", attachmentId) .body(new AttachmentInfo(URI.create("file:/home/fulanito.txt"))) - .put("/approvals/{processId}/firstLineApproval/{taskId}/attachments/{attachmentId}") + .put("/usertasks/instance/{taskId}/attachments/{attachmentId}") .then() .statusCode(200); @@ -226,10 +217,9 @@ void testCommentAndAttachment() { .when() .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) .pathParam("attachmentId", attachmentId) - .get("/approvals/{processId}/firstLineApproval/{taskId}/attachments/{attachmentId}") + .get("/usertasks/instance/{taskId}/attachments/{attachmentId}") .then() .statusCode(200).body("name", equalTo("fulanito.txt")).body("content", equalTo( "file:/home/fulanito.txt")); @@ -238,10 +228,9 @@ void testCommentAndAttachment() { .when() .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) .pathParam("attachmentId", attachmentId) - .delete("/approvals/{processId}/firstLineApproval/{taskId}/attachments/{attachmentId}") + .delete("/usertasks/instance/{taskId}/attachments/{attachmentId}") .then() .statusCode(200); @@ -249,10 +238,9 @@ void testCommentAndAttachment() { .when() .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) .pathParam("attachmentId", attachmentId) - .get("/approvals/{processId}/firstLineApproval/{taskId}/attachments/{attachmentId}") + .get("/usertasks/instance/{taskId}/attachments/{attachmentId}") .then() .statusCode(404); @@ -260,10 +248,9 @@ void testCommentAndAttachment() { .when() .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) .pathParam("attachmentId", attachmentId) - .delete("/approvals/{processId}/firstLineApproval/{taskId}/attachments/{attachmentId}") + .delete("/usertasks/instance/{taskId}/attachments/{attachmentId}") .then() .statusCode(404); } diff --git a/springboot/integration-tests/integration-tests-springboot-processes-persistence-it/integration-tests-springboot-processes-persistence-common/src/test/java/org/kie/kogito/it/PersistenceTest.java b/springboot/integration-tests/integration-tests-springboot-processes-persistence-it/integration-tests-springboot-processes-persistence-common/src/test/java/org/kie/kogito/it/PersistenceTest.java index 7ef8b40a8e8..15c79f08f94 100644 --- a/springboot/integration-tests/integration-tests-springboot-processes-persistence-it/integration-tests-springboot-processes-persistence-common/src/test/java/org/kie/kogito/it/PersistenceTest.java +++ b/springboot/integration-tests/integration-tests-springboot-processes-persistence-it/integration-tests-springboot-processes-persistence-common/src/test/java/org/kie/kogito/it/PersistenceTest.java @@ -46,6 +46,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.Matchers.emptyOrNullString; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -303,19 +304,31 @@ void testAdHocProcess() { .extract() .path("id"); - given().contentType(ContentType.JSON) + String location = given().contentType(ContentType.JSON) .pathParam("pId", pId) + .queryParam("user", "user") + .queryParam("group", "agroup") .when() - .get("/AdHocProcess/{pId}") + .post("/AdHocProcess/{pId}/CloseTask/trigger") .then() - .statusCode(200); + .log().everything() + .statusCode(201) + .header("Location", notNullValue()) + .extract() + .header("Location"); - given().contentType(ContentType.JSON) - .pathParam("pId", pId) + String taskId = location.substring(location.lastIndexOf("/") + 1); + + given() + .queryParam("user", "user") + .queryParam("group", "agroup") + .contentType(ContentType.JSON) .when() - .delete("/AdHocProcess/{pId}") + .body(Map.of("status", "closed")) + .post("/AdHocProcess/{pId}/CloseTask/{taskId}", pId, taskId) .then() - .statusCode(200); + .statusCode(200) + .body("status", equalTo("closed")); given().contentType(ContentType.JSON) .pathParam("pId", pId) From 29603fe97356f6e4761d71aacf49f1b1dba83a27 Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti <65240126+fjtirado@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:56:42 +0200 Subject: [PATCH 06/10] [Fix #1505] Avoid many instances of DefaultNodeInstanceFactory (#3689) --- .../instance/impl/CodegenNodeInstanceFactoryRegistry.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/CodegenNodeInstanceFactoryRegistry.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/CodegenNodeInstanceFactoryRegistry.java index e7c89eda05c..a3dd4f5f391 100644 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/CodegenNodeInstanceFactoryRegistry.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/impl/CodegenNodeInstanceFactoryRegistry.java @@ -24,11 +24,7 @@ public class CodegenNodeInstanceFactoryRegistry extends NodeInstanceFactoryRegistry { - @Override - protected NodeInstanceFactory get(Class clazz) { - if (SubProcessNode.class == clazz) { - return new DefaultNodeInstanceFactory(SubProcessNode.class, LambdaSubProcessNodeInstance::new); - } - return super.get(clazz); + public CodegenNodeInstanceFactoryRegistry() { + register(SubProcessNode.class, new DefaultNodeInstanceFactory(SubProcessNode.class, LambdaSubProcessNodeInstance::new)); } } From 3098229956b08c6b16092649899623402a9aed49 Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti <65240126+fjtirado@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:55:30 +0200 Subject: [PATCH 07/10] [Fix #3694] Avoid generation of fake ProcessInstanceVariableDataEvent (#3696) --- .../org/kie/kogito/expr/jq/JqExpression.java | 7 +- .../expr/jsonpath/JsonPathExpression.java | 38 +++++----- .../WorkflowJacksonJsonNodeJsonProvider.java | 7 +- .../handlers/CompositeContextNodeHandler.java | 4 +- .../StaticFluentWorkflowApplicationTest.java | 2 +- ...nNodeContext.java => VariablesHelper.java} | 72 ++++++++----------- .../main/resources/forEachCustomType.sw.json | 2 +- .../src/main/resources/forEachRest.sw.json | 2 +- .../src/main/resources/foreach_child.sw.json | 2 +- 9 files changed, 67 insertions(+), 69 deletions(-) rename kogito-serverless-workflow/kogito-serverless-workflow-utils/src/main/java/org/kie/kogito/serverless/workflow/utils/{JsonNodeContext.java => VariablesHelper.java} (70%) diff --git a/kogito-serverless-workflow/kogito-jq-expression/src/main/java/org/kie/kogito/expr/jq/JqExpression.java b/kogito-serverless-workflow/kogito-jq-expression/src/main/java/org/kie/kogito/expr/jq/JqExpression.java index 9d8c1676658..8039176cfe9 100644 --- a/kogito-serverless-workflow/kogito-jq-expression/src/main/java/org/kie/kogito/expr/jq/JqExpression.java +++ b/kogito-serverless-workflow/kogito-jq-expression/src/main/java/org/kie/kogito/expr/jq/JqExpression.java @@ -34,7 +34,7 @@ import org.kie.kogito.jackson.utils.PrefixJsonNode; import org.kie.kogito.process.expr.Expression; import org.kie.kogito.serverless.workflow.utils.ExpressionHandlerUtils; -import org.kie.kogito.serverless.workflow.utils.JsonNodeContext; +import org.kie.kogito.serverless.workflow.utils.VariablesHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -207,6 +207,7 @@ private Scope getScope(KogitoProcessContext processInfo) { childScope.setValue(ExpressionHandlerUtils.SECRET_MAGIC, new PrefixJsonNode<>(ExpressionHandlerUtils::getOptionalSecret)); childScope.setValue(ExpressionHandlerUtils.CONTEXT_MAGIC, new FunctionJsonNode(ExpressionHandlerUtils.getContextFunction(processInfo))); childScope.setValue(ExpressionHandlerUtils.CONST_MAGIC, ExpressionHandlerUtils.getConstants(processInfo)); + VariablesHelper.getAdditionalVariables(processInfo).forEach(childScope::setValue); return childScope; } @@ -215,8 +216,8 @@ private T eval(JsonNode context, Class returnClass, KogitoProcessContext throw new IllegalArgumentException("Unable to evaluate content " + context + " using expr " + expr, validationError); } TypedOutput output = output(returnClass); - try (JsonNodeContext jsonNode = JsonNodeContext.from(context, processInfo)) { - internalExpr.apply(getScope(processInfo), jsonNode.getNode(), output); + try { + internalExpr.apply(getScope(processInfo), context, output); return JsonObjectUtils.convertValue(output.getResult(), returnClass); } catch (JsonQueryException e) { throw new IllegalArgumentException("Unable to evaluate content " + context + " using expr " + expr, e); diff --git a/kogito-serverless-workflow/kogito-jsonpath-expression/src/main/java/org/kie/kogito/expr/jsonpath/JsonPathExpression.java b/kogito-serverless-workflow/kogito-jsonpath-expression/src/main/java/org/kie/kogito/expr/jsonpath/JsonPathExpression.java index 57952675962..5c6ed26968f 100644 --- a/kogito-serverless-workflow/kogito-jsonpath-expression/src/main/java/org/kie/kogito/expr/jsonpath/JsonPathExpression.java +++ b/kogito-serverless-workflow/kogito-jsonpath-expression/src/main/java/org/kie/kogito/expr/jsonpath/JsonPathExpression.java @@ -18,15 +18,17 @@ */ package org.kie.kogito.expr.jsonpath; +import java.util.Map; + import org.kie.kogito.internal.process.runtime.KogitoProcessContext; import org.kie.kogito.jackson.utils.JsonObjectUtils; import org.kie.kogito.process.expr.Expression; import org.kie.kogito.serverless.workflow.utils.ExpressionHandlerUtils; -import org.kie.kogito.serverless.workflow.utils.JsonNodeContext; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.DocumentContext; import com.jayway.jsonpath.JsonPath; @@ -61,24 +63,28 @@ private Configuration getConfiguration(KogitoProcessContext context) { .build(); } + private static boolean isContextAware(JsonNode context, Map additionalVars) { + return !additionalVars.isEmpty() && context instanceof ObjectNode; + } + private T eval(JsonNode context, Class returnClass, KogitoProcessContext processInfo) { - try (JsonNodeContext jsonNode = JsonNodeContext.from(context, processInfo)) { - Configuration jsonPathConfig = getConfiguration(processInfo); - DocumentContext parsedContext = JsonPath.using(jsonPathConfig).parse(jsonNode.getNode()); - if (String.class.isAssignableFrom(returnClass)) { - StringBuilder sb = new StringBuilder(); - // valid json path is $. or $[ - for (String part : expr.split("((?=\\$\\.|\\$\\[))")) { - JsonNode partResult = parsedContext.read(part, JsonNode.class); - sb.append(partResult.isTextual() ? partResult.asText() : partResult.toPrettyString()); - } - return (T) sb.toString(); - } else { - Object result = parsedContext.read(expr); - return Boolean.class.isAssignableFrom(returnClass) && result instanceof ArrayNode ? (T) Boolean.valueOf(!((ArrayNode) result).isEmpty()) - : JsonObjectUtils.convertValue(jsonPathConfig.mappingProvider().map(result, returnClass, jsonPathConfig), returnClass); + + Configuration jsonPathConfig = getConfiguration(processInfo); + DocumentContext parsedContext = JsonPath.using(jsonPathConfig).parse(context); + if (String.class.isAssignableFrom(returnClass)) { + StringBuilder sb = new StringBuilder(); + // valid json path is $. or $[ + for (String part : expr.split("((?=\\$\\.|\\$\\[))")) { + JsonNode partResult = parsedContext.read(part, JsonNode.class); + sb.append(partResult.isTextual() ? partResult.asText() : partResult.toPrettyString()); } + return (T) sb.toString(); + } else { + Object result = parsedContext.read(expr); + return Boolean.class.isAssignableFrom(returnClass) && result instanceof ArrayNode ? (T) Boolean.valueOf(!((ArrayNode) result).isEmpty()) + : JsonObjectUtils.convertValue(jsonPathConfig.mappingProvider().map(result, returnClass, jsonPathConfig), returnClass); } + } private void assign(JsonNode context, Object value, KogitoProcessContext processInfo) { diff --git a/kogito-serverless-workflow/kogito-jsonpath-expression/src/main/java/org/kie/kogito/expr/jsonpath/WorkflowJacksonJsonNodeJsonProvider.java b/kogito-serverless-workflow/kogito-jsonpath-expression/src/main/java/org/kie/kogito/expr/jsonpath/WorkflowJacksonJsonNodeJsonProvider.java index f423bc5c9eb..7a254728575 100644 --- a/kogito-serverless-workflow/kogito-jsonpath-expression/src/main/java/org/kie/kogito/expr/jsonpath/WorkflowJacksonJsonNodeJsonProvider.java +++ b/kogito-serverless-workflow/kogito-jsonpath-expression/src/main/java/org/kie/kogito/expr/jsonpath/WorkflowJacksonJsonNodeJsonProvider.java @@ -18,21 +18,26 @@ */ package org.kie.kogito.expr.jsonpath; +import java.util.Map; import java.util.function.Function; import org.kie.kogito.internal.process.runtime.KogitoProcessContext; import org.kie.kogito.jackson.utils.FunctionBaseJsonNode; import org.kie.kogito.jackson.utils.PrefixJsonNode; import org.kie.kogito.serverless.workflow.utils.ExpressionHandlerUtils; +import org.kie.kogito.serverless.workflow.utils.VariablesHelper; +import com.fasterxml.jackson.databind.JsonNode; import com.jayway.jsonpath.spi.json.JacksonJsonNodeJsonProvider; public class WorkflowJacksonJsonNodeJsonProvider extends JacksonJsonNodeJsonProvider { private KogitoProcessContext context; + private Map variables; public WorkflowJacksonJsonNodeJsonProvider(KogitoProcessContext context) { this.context = context; + this.variables = VariablesHelper.getAdditionalVariables(context); } @Override @@ -50,7 +55,7 @@ public Object getMapValue(Object obj, String key) { case "$" + ExpressionHandlerUtils.CONST_MAGIC: return ExpressionHandlerUtils.getConstants(context); default: - return super.getMapValue(obj, key); + return variables.containsKey(key) ? variables.get(key) : super.getMapValue(obj, key); } } } diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/handlers/CompositeContextNodeHandler.java b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/handlers/CompositeContextNodeHandler.java index d5b473bef8c..755f00e21aa 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/handlers/CompositeContextNodeHandler.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-builder/src/main/java/org/kie/kogito/serverless/workflow/parser/handlers/CompositeContextNodeHandler.java @@ -37,7 +37,7 @@ import org.kie.kogito.serverless.workflow.parser.FunctionTypeHandlerFactory; import org.kie.kogito.serverless.workflow.parser.ParserContext; import org.kie.kogito.serverless.workflow.parser.VariableInfo; -import org.kie.kogito.serverless.workflow.utils.JsonNodeContext; +import org.kie.kogito.serverless.workflow.utils.VariablesHelper; import io.serverlessworkflow.api.Workflow; import io.serverlessworkflow.api.actions.Action; @@ -181,7 +181,7 @@ private TimerNodeFactory createTimerNode(RuleFlowNodeContainerFactory f factory.subProcessNode(parserContext.newId()).name(subFlowRef.getWorkflowId()).processId(subFlowRef.getWorkflowId()).waitForCompletion(true), inputVar, outputVar); - JsonNodeContext.getEvalVariables(factory.getNode()).forEach(v -> subProcessNode.inMapping(v.getName(), v.getName())); + VariablesHelper.getEvalVariables(factory.getNode()).forEach(v -> subProcessNode.inMapping(v.getName(), v.getName())); return subProcessNode; } diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-executor-core/src/test/java/org/kie/kogito/serverless/workflow/executor/StaticFluentWorkflowApplicationTest.java b/kogito-serverless-workflow/kogito-serverless-workflow-executor-core/src/test/java/org/kie/kogito/serverless/workflow/executor/StaticFluentWorkflowApplicationTest.java index dcd49e1bc9a..1cc91bde1a2 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-executor-core/src/test/java/org/kie/kogito/serverless/workflow/executor/StaticFluentWorkflowApplicationTest.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-executor-core/src/test/java/org/kie/kogito/serverless/workflow/executor/StaticFluentWorkflowApplicationTest.java @@ -99,7 +99,7 @@ void testExpr() { void testForEach() { final String SQUARE = "square"; try (StaticWorkflowApplication application = StaticWorkflowApplication.create()) { - Workflow subflow = workflow("Square").start(operation().action(call(expr(SQUARE, ".input*.input"))).outputFilter(".response")).end().build(); + Workflow subflow = workflow("Square").start(operation().action(call(expr(SQUARE, "$input*$input"))).outputFilter(".response")).end().build(); Workflow workflow = workflow("ForEachTest") .start(forEach(".numbers").loopVar("input").outputCollection(".result").action(subprocess(application.process(subflow))) diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-utils/src/main/java/org/kie/kogito/serverless/workflow/utils/JsonNodeContext.java b/kogito-serverless-workflow/kogito-serverless-workflow-utils/src/main/java/org/kie/kogito/serverless/workflow/utils/VariablesHelper.java similarity index 70% rename from kogito-serverless-workflow/kogito-serverless-workflow-utils/src/main/java/org/kie/kogito/serverless/workflow/utils/JsonNodeContext.java rename to kogito-serverless-workflow/kogito-serverless-workflow-utils/src/main/java/org/kie/kogito/serverless/workflow/utils/VariablesHelper.java index bb98c574cca..92696f7e352 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-utils/src/main/java/org/kie/kogito/serverless/workflow/utils/JsonNodeContext.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-utils/src/main/java/org/kie/kogito/serverless/workflow/utils/VariablesHelper.java @@ -22,7 +22,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; -import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -41,14 +40,33 @@ import org.kie.kogito.internal.process.runtime.KogitoNodeInstance; import org.kie.kogito.internal.process.runtime.KogitoProcessContext; import org.kie.kogito.jackson.utils.JsonObjectUtils; +import org.kie.kogito.serverless.workflow.SWFConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -public class JsonNodeContext implements AutoCloseable { +public class VariablesHelper { - private final JsonNode jsonNode; - private final Set keys; + private static final Set PREDEFINED_KEYS = Set.of(SWFConstants.DEFAULT_WORKFLOW_VAR, SWFConstants.INPUT_WORKFLOW_VAR); + private static final Logger logger = LoggerFactory.getLogger(VariablesHelper.class); + + private VariablesHelper() { + } + + public static Map getAdditionalVariables(KogitoProcessContext context) { + Map variables = new HashMap<>(); + KogitoNodeInstance nodeInstance = context.getNodeInstance(); + if (nodeInstance != null) { + NodeInstanceContainer container = nodeInstance instanceof NodeInstanceContainer ? (NodeInstanceContainer) nodeInstance : nodeInstance.getNodeInstanceContainer(); + while (container instanceof ContextableInstance) { + addVariablesFromContext((ContextableInstance) container, variables); + container = container instanceof KogitoNodeInstance ? ((KogitoNodeInstance) container).getNodeInstanceContainer() : null; + } + } + logger.debug("Additional variables for expression evaluation are {}", variables); + return variables; + } public static Stream getEvalVariables(Node node) { if (node instanceof ForEachNode) { @@ -66,55 +84,23 @@ private static Stream getEvalVariables(ContextableInstance containerIn private static Stream getEvalVariables(ContextContainer container) { VariableScope variableScope = (VariableScope) container.getDefaultContext(VariableScope.VARIABLE_SCOPE); - return variableScope.getVariables().stream().filter(v -> v.getMetaData(Metadata.EVAL_VARIABLE) != null); - } - - public static JsonNodeContext from(JsonNode jsonNode, KogitoProcessContext context) { - Map map = new HashMap<>(); - if (jsonNode.isObject()) { - ObjectNode objectNode = (ObjectNode) jsonNode; - addVariablesFromContext(objectNode, context, map); - } - return new JsonNodeContext(jsonNode, map.keySet()); + return variableScope.getVariables().stream().filter(VariablesHelper::isEvalVariable); } - public JsonNode getNode() { - return jsonNode; + private static boolean isEvalVariable(Variable v) { + Object isEval = v.getMetaData(Metadata.EVAL_VARIABLE); + return isEval instanceof Boolean ? ((Boolean) isEval).booleanValue() : false; } - private JsonNodeContext(JsonNode jsonNode, Set keys) { - this.jsonNode = jsonNode; - this.keys = keys; - } - - private static void addVariablesFromContext(ObjectNode jsonNode, KogitoProcessContext processInfo, Map variables) { - KogitoNodeInstance nodeInstance = processInfo.getNodeInstance(); - if (nodeInstance != null) { - NodeInstanceContainer container = nodeInstance instanceof NodeInstanceContainer ? (NodeInstanceContainer) nodeInstance : nodeInstance.getNodeInstanceContainer(); - while (container instanceof ContextableInstance) { - getVariablesFromContext(jsonNode, (ContextableInstance) container, variables); - container = container instanceof KogitoNodeInstance ? ((KogitoNodeInstance) container).getNodeInstanceContainer() : null; - } - } - variables.forEach(jsonNode::set); - } - - private static void getVariablesFromContext(ObjectNode jsonNode, ContextableInstance node, Map variables) { + private static void addVariablesFromContext(ContextableInstance node, Map variables) { VariableScopeInstance variableScope = (VariableScopeInstance) node.getContextInstance(VariableScope.VARIABLE_SCOPE); if (variableScope != null) { Collection evalVariables = getEvalVariables(node).map(Variable::getName).collect(Collectors.toList()); for (Entry e : variableScope.getVariables().entrySet()) { - if (evalVariables.contains(e.getKey()) || node instanceof WorkflowProcessInstance && !Objects.equals(jsonNode, e.getValue())) { + if (evalVariables.contains(e.getKey()) || node instanceof WorkflowProcessInstance && !PREDEFINED_KEYS.contains(e.getKey())) { variables.putIfAbsent(e.getKey(), JsonObjectUtils.fromValue(e.getValue())); } } } } - - @Override - public void close() { - if (!keys.isEmpty()) { - keys.forEach(((ObjectNode) jsonNode)::remove); - } - } } diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/forEachCustomType.sw.json b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/forEachCustomType.sw.json index dd7cd214ade..988e6f29909 100644 --- a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/forEachCustomType.sw.json +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/forEachCustomType.sw.json @@ -22,7 +22,7 @@ "functionRef": { "refName": "division", "arguments": { - "dividend": ".item", + "dividend": "$item", "divisor" : ".divisor" } }, diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/forEachRest.sw.json b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/forEachRest.sw.json index 72cc34f32d6..04c0c6e3d00 100644 --- a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/forEachRest.sw.json +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/forEachRest.sw.json @@ -22,7 +22,7 @@ "functionRef": { "refName": "division", "arguments": { - "QUERY_dividend": ".item", + "QUERY_dividend": "$item", "QUERY_divisor" : ".divisor" } }, diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/foreach_child.sw.json b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/foreach_child.sw.json index d1b25583f90..15ce29a016a 100644 --- a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/foreach_child.sw.json +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/foreach_child.sw.json @@ -9,7 +9,7 @@ { "name": "multiply", "type": "expression", - "operation": ".number*.constant" + "operation": "$number*.constant" } ], "states": [ From b887866ebc27fb8a085b1e5861948b93357c53c8 Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti <65240126+fjtirado@users.noreply.github.com> Date: Thu, 10 Oct 2024 12:16:15 +0200 Subject: [PATCH 08/10] [Fix #3709] Inject action node should be cloned before merge (#3710) * [Fix #3709] Allow cloning when source node is the merge result * Revert "[Fix #3709] Allow cloning when source node is the merge result" This reverts commit f0a9cbac4e3ffe631227ed6bd6e8da1b787ceb37. * [Fix #3709] Clone node when injecting --- .../kie/kogito/serverless/workflow/actions/InjectAction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/actions/InjectAction.java b/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/actions/InjectAction.java index e67a2f85b37..de05503dcba 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/actions/InjectAction.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/actions/InjectAction.java @@ -51,6 +51,6 @@ private static JsonNode readObject(String json) { @Override public void execute(KogitoProcessContext context) throws Exception { - context.setVariable(SWFConstants.DEFAULT_WORKFLOW_VAR, MergeUtils.merge(node, getWorkflowData(context))); + context.setVariable(SWFConstants.DEFAULT_WORKFLOW_VAR, MergeUtils.merge(node.deepCopy(), getWorkflowData(context))); } } From 6f869c811ba37fd85233ad984102f729bb84f436 Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti <65240126+fjtirado@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:46:51 +0200 Subject: [PATCH 09/10] [Fix #3712] Allow concatenation of secrets and const (#3713) --- .../java/org/kie/kogito/expr/jsonpath/JsonPathExpression.java | 2 +- .../kogito/expr/jsonpath/JsonPathExpressionHandlerTest.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/kogito-serverless-workflow/kogito-jsonpath-expression/src/main/java/org/kie/kogito/expr/jsonpath/JsonPathExpression.java b/kogito-serverless-workflow/kogito-jsonpath-expression/src/main/java/org/kie/kogito/expr/jsonpath/JsonPathExpression.java index 5c6ed26968f..497d3bc4a93 100644 --- a/kogito-serverless-workflow/kogito-jsonpath-expression/src/main/java/org/kie/kogito/expr/jsonpath/JsonPathExpression.java +++ b/kogito-serverless-workflow/kogito-jsonpath-expression/src/main/java/org/kie/kogito/expr/jsonpath/JsonPathExpression.java @@ -52,7 +52,7 @@ public JsonPathExpression(String expr) { private static final String replaceMagic(String expr, String magic) { magic = "$" + magic; - return expr.replace(magic, "@." + magic); + return expr.replace(magic, "$." + magic); } private Configuration getConfiguration(KogitoProcessContext context) { diff --git a/kogito-serverless-workflow/kogito-jsonpath-expression/src/test/java/org/kie/kogito/expr/jsonpath/JsonPathExpressionHandlerTest.java b/kogito-serverless-workflow/kogito-jsonpath-expression/src/test/java/org/kie/kogito/expr/jsonpath/JsonPathExpressionHandlerTest.java index 7d364f0ba12..aea64e4688f 100644 --- a/kogito-serverless-workflow/kogito-jsonpath-expression/src/test/java/org/kie/kogito/expr/jsonpath/JsonPathExpressionHandlerTest.java +++ b/kogito-serverless-workflow/kogito-jsonpath-expression/src/test/java/org/kie/kogito/expr/jsonpath/JsonPathExpressionHandlerTest.java @@ -230,7 +230,7 @@ void testWorkflowPropertyFromJsonAccessible() { @MethodSource("provideMagicWordExpressionsToTest") void testMagicWordsExpressions(String expression, String expectedResult, KogitoProcessContext context) { Expression parsedExpression = ExpressionHandlerFactory.get("jsonpath", expression); - assertThat(parsedExpression.isValid()).isTrue(); + assertThat(parsedExpression.isValid()).overridingErrorMessage(() -> parsedExpression.validationError().getMessage()).isTrue(); assertThat(parsedExpression.eval(getObjectNode(), String.class, context)).isEqualTo(expectedResult); } @@ -238,6 +238,8 @@ private static Stream provideMagicWordExpressionsToTest() { return Stream.of( Arguments.of("$WORKFLOW.instanceId", "1111-2222-3333", getContext()), Arguments.of("$SECRET.lettersonly", "secretlettersonly", getContext()), + Arguments.of("$SECRET.lettersonly$SECRET.lettersonly", "secretlettersonlysecretlettersonly", getContext()), + Arguments.of("$.propertyString$.propertyNum", "string12", getContext()), Arguments.of("$SECRET.none", "", getContext()), Arguments.of("$SECRET.dot.secret", "secretdotsecret", getContext()), Arguments.of("$SECRET[\"dot.secret\"]", "secretdotsecret", getContext()), From cc6557d2d421b54c4c45ad8231344e98d609a5f2 Mon Sep 17 00:00:00 2001 From: Roberto Oliveira Date: Thu, 10 Oct 2024 12:59:22 -0400 Subject: [PATCH 10/10] NO-ISSUE: remove legacy productized-db-scripts.xml file (#3715) --- addons/common/persistence/ddl/pom.xml | 13 ------- .../src/assembly/productized-db-scripts.xml | 38 ------------------- 2 files changed, 51 deletions(-) delete mode 100644 addons/common/persistence/ddl/src/assembly/productized-db-scripts.xml diff --git a/addons/common/persistence/ddl/pom.xml b/addons/common/persistence/ddl/pom.xml index a96bf50824c..7a6519a2fbf 100644 --- a/addons/common/persistence/ddl/pom.xml +++ b/addons/common/persistence/ddl/pom.xml @@ -62,17 +62,4 @@ - - - productized - - - productized - - - - src/assembly/productized-db-scripts.xml - - - \ No newline at end of file diff --git a/addons/common/persistence/ddl/src/assembly/productized-db-scripts.xml b/addons/common/persistence/ddl/src/assembly/productized-db-scripts.xml deleted file mode 100644 index 39ec204674d..00000000000 --- a/addons/common/persistence/ddl/src/assembly/productized-db-scripts.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - db-scripts - - zip - - false - - - ${path.to.persistence.modules}/jdbc/src/main/resources/kie-flyway/db/postgresql - postgresql - - *.sql - - - -