diff --git a/.github/workflows/pr-kogito-runtimes.yml b/.github/workflows/pr-kogito-runtimes.yml index de79abcccaf..1e0bbdb0764 100644 --- a/.github/workflows/pr-kogito-runtimes.yml +++ b/.github/workflows/pr-kogito-runtimes.yml @@ -46,7 +46,7 @@ jobs: github-token: "${{ secrets.GITHUB_TOKEN }}" definition-file: https://raw.githubusercontent.com/${GROUP:kiegroup}/kogito-pipelines/${BRANCH:main}/.ci/pull-request-config.yaml env: - BUILD_MVN_OPTS_CURRENT: '-T 1.5C' + BUILD_MVN_OPTS_CURRENT: '-Dvalidate-formatting' - name: Junit Report uses: kiegroup/kogito-pipelines/.ci/actions/action-junit-report@main if: ${{ always() }} diff --git a/DISCLAIMER b/DISCLAIMER-WIP similarity index 100% rename from DISCLAIMER rename to DISCLAIMER-WIP diff --git a/addons/common/monitoring/core/src/main/java/org/kie/kogito/monitoring/core/common/process/MetricsProcessEventListener.java b/addons/common/monitoring/core/src/main/java/org/kie/kogito/monitoring/core/common/process/MetricsProcessEventListener.java index 007c1aa4bde..1de0307b1af 100644 --- a/addons/common/monitoring/core/src/main/java/org/kie/kogito/monitoring/core/common/process/MetricsProcessEventListener.java +++ b/addons/common/monitoring/core/src/main/java/org/kie/kogito/monitoring/core/common/process/MetricsProcessEventListener.java @@ -179,7 +179,7 @@ public void beforeNodeLeft(ProcessNodeLeftEvent event) { } private void recordNodeDuration(DistributionSummary summary, KogitoNodeInstance instance, TimeUnit target) { - if (instance.getTriggerTime() != null) { + if (instance.getTriggerTime() != null && instance.getLeaveTime() != null) { double duration = target.convert(instance.getLeaveTime().getTime() - instance.getTriggerTime().getTime(), TimeUnit.MILLISECONDS); summary.record(duration); LOGGER.debug("Recorded {} {} because of node {} for summary {}", duration, target, instance.getNode().getName(), summary.getId().getName()); diff --git a/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/BaseExceptionsHandler.java b/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/BaseExceptionsHandler.java index 3da864ee7eb..c326eeebe76 100644 --- a/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/BaseExceptionsHandler.java +++ b/addons/common/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/BaseExceptionsHandler.java @@ -35,6 +35,9 @@ import org.kie.kogito.process.ProcessInstanceExecutionException; import org.kie.kogito.process.ProcessInstanceNotFoundException; import org.kie.kogito.process.VariableViolationException; +import org.kie.kogito.usertask.UserTaskInstanceNotAuthorizedException; +import org.kie.kogito.usertask.UserTaskInstanceNotFoundException; +import org.kie.kogito.usertask.lifecycle.UserTaskTransitionException; public abstract class BaseExceptionsHandler { @@ -74,6 +77,15 @@ protected BaseExceptionsHandler() { mapper.put(InvalidLifeCyclePhaseException.class, new FunctionHolder<>( ex -> Collections.singletonMap(MESSAGE, ex.getMessage()), ex -> BaseExceptionsHandler.this::badRequest)); + mapper.put(UserTaskTransitionException.class, new FunctionHolder<>( + ex -> Collections.singletonMap(MESSAGE, ex.getMessage()), ex -> BaseExceptionsHandler.this::badRequest)); + + mapper.put(UserTaskInstanceNotFoundException.class, new FunctionHolder<>( + ex -> Collections.singletonMap(MESSAGE, ex.getMessage()), ex -> BaseExceptionsHandler.this::notFound)); + + mapper.put(UserTaskInstanceNotAuthorizedException.class, new FunctionHolder<>( + ex -> Collections.singletonMap(MESSAGE, ex.getMessage()), ex -> BaseExceptionsHandler.this::forbidden)); + mapper.put(InvalidTransitionException.class, new FunctionHolder<>( ex -> Collections.singletonMap(MESSAGE, ex.getMessage()), ex -> BaseExceptionsHandler.this::badRequest)); diff --git a/addons/common/task-management/src/main/java/org/kie/kogito/task/management/service/TaskInfo.java b/addons/common/task-management/src/main/java/org/kie/kogito/task/management/service/TaskInfo.java index dd61e635366..30af391df90 100644 --- a/addons/common/task-management/src/main/java/org/kie/kogito/task/management/service/TaskInfo.java +++ b/addons/common/task-management/src/main/java/org/kie/kogito/task/management/service/TaskInfo.java @@ -19,6 +19,7 @@ package org.kie.kogito.task.management.service; import java.util.Map; +import java.util.Objects; import java.util.Set; public class TaskInfo { @@ -112,6 +113,25 @@ public void setInputParams(Map inputParams) { this.inputParams = inputParams; } + @Override + public int hashCode() { + return Objects.hash(adminGroups, adminUsers, description, excludedUsers, inputParams, potentialGroups, potentialUsers, priority); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TaskInfo other = (TaskInfo) obj; + return Objects.equals(adminGroups, other.adminGroups) && Objects.equals(adminUsers, other.adminUsers) && Objects.equals(description, other.description) + && Objects.equals(excludedUsers, other.excludedUsers) && Objects.equals(inputParams, other.inputParams) && Objects.equals(potentialGroups, other.potentialGroups) + && Objects.equals(potentialUsers, other.potentialUsers) && Objects.equals(priority, other.priority); + } + @Override public String toString() { return "TaskInfo [description=" + description + ", priority=" + priority + ", potentialUsers=" + diff --git a/addons/common/task-management/src/main/java/org/kie/kogito/task/management/service/TaskManagementOperations.java b/addons/common/task-management/src/main/java/org/kie/kogito/task/management/service/TaskManagementOperations.java index 6e5f7711d86..185cfe4ac05 100644 --- a/addons/common/task-management/src/main/java/org/kie/kogito/task/management/service/TaskManagementOperations.java +++ b/addons/common/task-management/src/main/java/org/kie/kogito/task/management/service/TaskManagementOperations.java @@ -18,16 +18,9 @@ */ package org.kie.kogito.task.management.service; -import org.kie.kogito.internal.process.workitem.Policy; - public interface TaskManagementOperations { - TaskInfo updateTask(String processId, - String processInstanceId, - String taskId, - TaskInfo taskInfo, - boolean replace, - Policy... policies); + TaskInfo updateTask(String taskId, TaskInfo taskInfo, boolean replace); - TaskInfo getTask(String processId, String processInstanceId, String taskId, Policy... policies); + TaskInfo getTask(String taskId); } diff --git a/addons/common/task-management/src/main/java/org/kie/kogito/task/management/service/TaskManagementService.java b/addons/common/task-management/src/main/java/org/kie/kogito/task/management/service/TaskManagementService.java index 450dc30d0b1..f834191c9ad 100644 --- a/addons/common/task-management/src/main/java/org/kie/kogito/task/management/service/TaskManagementService.java +++ b/addons/common/task-management/src/main/java/org/kie/kogito/task/management/service/TaskManagementService.java @@ -18,50 +18,63 @@ */ package org.kie.kogito.task.management.service; -import java.util.Collections; import java.util.Map; import java.util.Map.Entry; -import java.util.Set; +import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.function.Supplier; -import org.kie.kogito.internal.process.workitem.KogitoWorkItem; -import org.kie.kogito.internal.process.workitem.Policy; -import org.kie.kogito.process.Process; import org.kie.kogito.process.ProcessConfig; -import org.kie.kogito.process.ProcessInstance; -import org.kie.kogito.process.ProcessInstanceNotFoundException; -import org.kie.kogito.process.Processes; -import org.kie.kogito.process.WorkItem; -import org.kie.kogito.process.workitems.InternalKogitoWorkItem; import org.kie.kogito.services.uow.UnitOfWorkExecutor; +import org.kie.kogito.usertask.UserTaskConfig; +import org.kie.kogito.usertask.UserTaskInstance; +import org.kie.kogito.usertask.UserTaskInstanceNotFoundException; +import org.kie.kogito.usertask.UserTasks; +import org.kie.kogito.usertask.impl.DefaultUserTaskInstance; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class TaskManagementService implements TaskManagementOperations { - private Processes processes; - private ProcessConfig processConfig; + private static final Logger LOG = LoggerFactory.getLogger(TaskManagementService.class); - public TaskManagementService(Processes processes, ProcessConfig processConfig) { - this.processes = processes; - this.processConfig = processConfig; + private UserTasks userTasks; + // unit of work needs to add the publisher and this is not shared. + private UserTaskConfig userTaskConfig; + private ProcessConfig processesConfig; + + public TaskManagementService(UserTasks userTasks, UserTaskConfig userTaskConfig, ProcessConfig processConfig) { + this.userTasks = userTasks; + this.userTaskConfig = userTaskConfig; + this.processesConfig = processConfig; } @Override - public TaskInfo updateTask(String processId, - String processInstanceId, - String taskId, - TaskInfo taskInfo, - boolean shouldReplace, - Policy... policies) { - ProcessInstance pi = getProcessInstance(processId, processInstanceId, taskId); - KogitoWorkItem workItem = UnitOfWorkExecutor.executeInUnitOfWork(processConfig.unitOfWorkManager(), - () -> pi.updateWorkItem(taskId, - wi -> { - InternalKogitoWorkItem task = (InternalKogitoWorkItem) wi; - setMap(task::setParameters, task::setParameter, taskInfo.getInputParams(), shouldReplace); - return wi; - }, policies)); - return convert(workItem); + public TaskInfo updateTask(String taskId, TaskInfo taskInfo, boolean shouldReplace) { + UserTaskInstance userTaskInstance = UnitOfWorkExecutor.executeInUnitOfWork(processesConfig.unitOfWorkManager(), () -> { + DefaultUserTaskInstance ut = (DefaultUserTaskInstance) getUserTaskInstance(taskId); + setField(ut::setTaskDescription, taskInfo::getDescription, shouldReplace); + setField(ut::setTaskPriority, taskInfo::getPriority, shouldReplace); + setField(ut::setAdminGroups, taskInfo::getAdminGroups, shouldReplace); + setField(ut::setAdminUsers, taskInfo::getAdminUsers, shouldReplace); + setField(ut::setExcludedUsers, taskInfo::getExcludedUsers, shouldReplace); + setField(ut::setPotentialUsers, taskInfo::getPotentialUsers, shouldReplace); + setField(ut::setPotentialGroups, taskInfo::getPotentialGroups, shouldReplace); + setMap(ut::setInputs, ut::setInput, taskInfo.getInputParams(), shouldReplace); + return ut; + }); + LOG.trace("updated task through management endpoint to {}", userTaskInstance); + return convert(userTaskInstance); + } + + private boolean setField(Consumer consumer, Supplier supplier, boolean shouldReplace) { + T value = supplier.get(); + boolean result = shouldReplace || value != null; + if (result) { + consumer.accept(value); + } + return result; } private void setMap(Consumer> allConsumer, @@ -80,61 +93,31 @@ private void setMap(Consumer> allConsumer, } @Override - public TaskInfo getTask(String processId, String processInstanceId, String taskId, Policy... policies) { - WorkItem workItem = getProcessInstance(processId, processInstanceId, taskId).workItem(taskId, policies); - return convert(workItem); - } - - private TaskInfo convert(WorkItem workItem) { - return new TaskInfo( - (String) workItem.getParameters().get("Description"), - (String) workItem.getParameters().get("Priority"), - toSet(workItem.getParameters().get("ActorId")), - toSet(workItem.getParameters().get("GroupId")), - toSet(workItem.getParameters().get("ExcludedUsersId")), - toSet(workItem.getParameters().get("BusinessAdministratorId")), - toSet(workItem.getParameters().get("BusinessGroupsId")), - workItem.getParameters()); + public TaskInfo getTask(String taskId) { + return convert(getUserTaskInstance(taskId)); } - private TaskInfo convert(KogitoWorkItem workItem) { + private TaskInfo convert(UserTaskInstance userTaskInstance) { return new TaskInfo( - (String) workItem.getParameter("Description"), - (String) workItem.getParameter("Priority"), - toSet(workItem.getParameter("ActorId")), - toSet(workItem.getParameter("GroupId")), - toSet(workItem.getParameter("ExcludedUsersId")), - toSet(workItem.getParameter("BusinessAdministratorId")), - toSet(workItem.getParameter("BusinessGroupsId")), - workItem.getParameters()); + userTaskInstance.getTaskDescription(), + userTaskInstance.getTaskPriority(), + userTaskInstance.getPotentialUsers(), + userTaskInstance.getPotentialGroups(), + userTaskInstance.getExcludedUsers(), + userTaskInstance.getAdminUsers(), + userTaskInstance.getAdminGroups(), + userTaskInstance.getInputs()); } - private Set toSet(Object value) { - if (value == null) { - return Collections.emptySet(); - } - if (value instanceof String string) { - return Set.of(string.split(",")); - } - return Collections.emptySet(); - } - - private ProcessInstance getProcessInstance(String processId, String processInstanceId, String taskId) { - if (processId == null) { - throw new IllegalArgumentException("Process id must be given"); - } - if (processInstanceId == null) { - throw new IllegalArgumentException("Process instance id must be given"); - } + private UserTaskInstance getUserTaskInstance(String taskId) { if (taskId == null) { throw new IllegalArgumentException("Task id must be given"); } - Process process = processes.processById(processId); - if (process == null) { - throw new IllegalArgumentException(String.format("Process with id %s not found", processId)); + Optional userTaskInstance = userTasks.instances().findById(taskId); + if (userTaskInstance.isEmpty()) { + throw new UserTaskInstanceNotFoundException(String.format("user task instance with id %s not found", taskId)); } - return process.instances().findById(processInstanceId).orElseThrow( - () -> new ProcessInstanceNotFoundException(processInstanceId)); + return userTaskInstance.get(); } } diff --git a/addons/common/tracing/decision-common/src/main/java/org/kie/kogito/tracing/decision/event/evaluate/EvaluateEventType.java b/addons/common/tracing/decision-common/src/main/java/org/kie/kogito/tracing/decision/event/evaluate/EvaluateEventType.java index a548c221d7d..eb13ac290db 100644 --- a/addons/common/tracing/decision-common/src/main/java/org/kie/kogito/tracing/decision/event/evaluate/EvaluateEventType.java +++ b/addons/common/tracing/decision-common/src/main/java/org/kie/kogito/tracing/decision/event/evaluate/EvaluateEventType.java @@ -21,10 +21,12 @@ import org.kie.kogito.tracing.event.trace.TraceExecutionStepType; public enum EvaluateEventType { + AFTER_CONDITIONAL_EVALUATION(false), BEFORE_EVALUATE_ALL(true), AFTER_EVALUATE_ALL(false), BEFORE_EVALUATE_BKM(true), AFTER_EVALUATE_BKM(false), + AFTER_EVALUATE_CONDITIONAL(false), BEFORE_EVALUATE_CONTEXT_ENTRY(true), AFTER_EVALUATE_CONTEXT_ENTRY(false), BEFORE_EVALUATE_DECISION(true), @@ -52,6 +54,10 @@ public boolean isAfter() { public TraceExecutionStepType toTraceExecutionStepType() { switch (this) { + case AFTER_CONDITIONAL_EVALUATION: + case AFTER_EVALUATE_CONDITIONAL: + return TraceExecutionStepType.DMN_CONDITIONAL_INVOCATION; + case BEFORE_EVALUATE_CONTEXT_ENTRY: case AFTER_EVALUATE_CONTEXT_ENTRY: return TraceExecutionStepType.DMN_CONTEXT_ENTRY; diff --git a/addons/common/tracing/decision-common/src/test/java/org/kie/kogito/tracing/decision/event/evaluate/EvaluateEventTypeTest.java b/addons/common/tracing/decision-common/src/test/java/org/kie/kogito/tracing/decision/event/evaluate/EvaluateEventTypeTest.java index 61b59ba968e..b5203d10773 100644 --- a/addons/common/tracing/decision-common/src/test/java/org/kie/kogito/tracing/decision/event/evaluate/EvaluateEventTypeTest.java +++ b/addons/common/tracing/decision-common/src/test/java/org/kie/kogito/tracing/decision/event/evaluate/EvaluateEventTypeTest.java @@ -24,8 +24,10 @@ import java.util.Optional; import org.junit.jupiter.api.Test; +import org.kie.dmn.api.core.event.AfterConditionalEvaluationEvent; import org.kie.dmn.api.core.event.AfterEvaluateAllEvent; import org.kie.dmn.api.core.event.AfterEvaluateBKMEvent; +import org.kie.dmn.api.core.event.AfterEvaluateConditionalEvent; import org.kie.dmn.api.core.event.AfterEvaluateContextEntryEvent; import org.kie.dmn.api.core.event.AfterEvaluateDecisionEvent; import org.kie.dmn.api.core.event.AfterEvaluateDecisionServiceEvent; @@ -67,6 +69,8 @@ class EvaluateEventTypeTest { put(EvaluateEventType.AFTER_EVALUATE_DECISION_TABLE, new Pair<>("afterEvaluateDecisionTable", AfterEvaluateDecisionTableEvent.class)); put(EvaluateEventType.BEFORE_INVOKE_BKM, new Pair<>("beforeInvokeBKM", BeforeInvokeBKMEvent.class)); put(EvaluateEventType.AFTER_INVOKE_BKM, new Pair<>("afterInvokeBKM", AfterInvokeBKMEvent.class)); + put(EvaluateEventType.AFTER_CONDITIONAL_EVALUATION, new Pair<>("afterConditionalEvaluation", AfterConditionalEvaluationEvent.class)); + put(EvaluateEventType.AFTER_EVALUATE_CONDITIONAL, new Pair<>("afterEvaluateConditional", AfterEvaluateConditionalEvent.class)); } }; private static final Class LISTENER_CLASS = DMNRuntimeEventListener.class; diff --git a/addons/common/tracing/tracing-api/src/main/java/org/kie/kogito/tracing/event/trace/TraceExecutionStepType.java b/addons/common/tracing/tracing-api/src/main/java/org/kie/kogito/tracing/event/trace/TraceExecutionStepType.java index 823231adc53..bccd24c7f27 100644 --- a/addons/common/tracing/tracing-api/src/main/java/org/kie/kogito/tracing/event/trace/TraceExecutionStepType.java +++ b/addons/common/tracing/tracing-api/src/main/java/org/kie/kogito/tracing/event/trace/TraceExecutionStepType.java @@ -24,6 +24,7 @@ public enum TraceExecutionStepType { DMN_BKM_EVALUATION, DMN_BKM_INVOCATION, + DMN_CONDITIONAL_INVOCATION, DMN_CONTEXT_ENTRY, DMN_DECISION, DMN_DECISION_SERVICE, diff --git a/api/kogito-api/src/main/java/org/kie/kogito/process/ProcessService.java b/api/kogito-api/src/main/java/org/kie/kogito/process/ProcessService.java index d6f6e79ceaa..42585a783bc 100644 --- a/api/kogito-api/src/main/java/org/kie/kogito/process/ProcessService.java +++ b/api/kogito-api/src/main/java/org/kie/kogito/process/ProcessService.java @@ -18,7 +18,6 @@ */ package org.kie.kogito.process; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -28,12 +27,8 @@ import org.kie.kogito.MapOutput; import org.kie.kogito.MappableToModel; import org.kie.kogito.Model; -import org.kie.kogito.auth.SecurityPolicy; import org.kie.kogito.correlation.CompositeCorrelation; import org.kie.kogito.internal.process.workitem.Policy; -import org.kie.kogito.usertask.model.Attachment; -import org.kie.kogito.usertask.model.AttachmentInfo; -import org.kie.kogito.usertask.model.Comment; public interface ProcessService { @@ -110,64 +105,4 @@ Map getWorkItemSchemaAndPhases(Process proc String taskName, Policy policy); - Optional addComment(Process process, - String id, - String taskId, - SecurityPolicy policy, - String commentInfo); - - Optional updateComment(Process process, - String id, - String taskId, - String commentId, - SecurityPolicy policy, - String commentInfo); - - Optional deleteComment(Process process, - String id, - String taskId, - String commentId, - SecurityPolicy policy); - - Optional addAttachment(Process process, - String id, - String taskId, - SecurityPolicy policy, - AttachmentInfo attachmentInfo); - - Optional updateAttachment(Process process, - String id, - String taskId, - String attachmentId, - SecurityPolicy policy, - AttachmentInfo attachment); - - Optional deleteAttachment(Process process, - String id, - String taskId, - String attachmentId, - SecurityPolicy policy); - - Optional getAttachment(Process process, - String id, - String taskId, - String attachmentId, - SecurityPolicy policy); - - Optional> getAttachments(Process process, - String id, - String taskId, - SecurityPolicy policy); - - Optional getComment(Process process, - String id, - String taskId, - String commentId, - SecurityPolicy policy); - - Optional> getComments(Process process, - String id, - String taskId, - SecurityPolicy policy); - } 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 11d762195a5..b07df5f2f35 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 @@ -57,7 +57,7 @@ public interface UserTask { * * @return task priority if present */ - Integer getTaskPriority(); + String getTaskPriority(); /** * Returns reference name of the task 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 4a0023a0c41..9e101eb9d4b 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 @@ -51,7 +51,7 @@ public interface UserTaskInstance { String getTaskDescription(); - Integer getTaskPriority(); + String getTaskPriority(); Map getMetadata(); diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstanceNotAuthorizedException.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstanceNotAuthorizedException.java new file mode 100644 index 00000000000..4eadba5789f --- /dev/null +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstanceNotAuthorizedException.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; + +public class UserTaskInstanceNotAuthorizedException extends RuntimeException { + + private static final long serialVersionUID = 6224742517975146469L; + + public UserTaskInstanceNotAuthorizedException() { + // do nothing + } + + public UserTaskInstanceNotAuthorizedException(String msg) { + super(msg); + } +} diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstanceNotFoundException.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstanceNotFoundException.java new file mode 100644 index 00000000000..66d0213a505 --- /dev/null +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstanceNotFoundException.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; + +public class UserTaskInstanceNotFoundException extends RuntimeException { + + private static final long serialVersionUID = 6224742517975146469L; + + public UserTaskInstanceNotFoundException() { + // do nothing + } + + public UserTaskInstanceNotFoundException(String id) { + super("User task Instance " + id + " + not found"); + } +} 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 d032eca9eaf..27f2fecbe01 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 @@ -20,9 +20,7 @@ import java.net.URI; -import org.kie.kogito.process.workitem.TaskMetaEntity; - -public class Attachment extends TaskMetaEntity { +public class Attachment extends UserTaskEntity { private static final long serialVersionUID = 1L; private String name; diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/AttachmentInfo.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/AttachmentInfo.java index c56ed6dd9dd..2f09ad8c5b1 100644 --- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/AttachmentInfo.java +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/AttachmentInfo.java @@ -37,6 +37,14 @@ public AttachmentInfo(URI uri, String name) { this.name = name; } + public void setUri(URI uri) { + this.uri = uri; + } + + public void setName(String name) { + this.name = name; + } + public URI getUri() { return uri; } @@ -44,4 +52,5 @@ public URI getUri() { public String getName() { return name; } + } 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 fe2a3fdf457..271cc3c75e6 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 @@ -18,9 +18,7 @@ */ package org.kie.kogito.usertask.model; -import org.kie.kogito.process.workitem.TaskMetaEntity; - -public class Comment extends TaskMetaEntity { +public class Comment extends UserTaskEntity { private static final long serialVersionUID = -9106249675352498780L; diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/TransitionInfo.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/TransitionInfo.java new file mode 100644 index 00000000000..c76ce87ce96 --- /dev/null +++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/model/TransitionInfo.java @@ -0,0 +1,58 @@ +/* + * 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; + +import java.util.Collections; +import java.util.Map; + +public class TransitionInfo { + + private String transitionId; + + private Map data; + + public TransitionInfo() { + // do nothing + } + + public TransitionInfo(String transitionId) { + this(transitionId, Collections.emptyMap()); + } + + public TransitionInfo(String transitionId, Map data) { + this.transitionId = transitionId; + this.data = data; + } + + public Map getData() { + return data; + } + + public void setData(Map data) { + this.data = data; + } + + public String getTransitionId() { + return transitionId; + } + + public void setTransitionId(String transitionId) { + this.transitionId = transitionId; + } +} 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/usertask/model/UserTaskEntity.java similarity index 90% rename from api/kogito-api/src/main/java/org/kie/kogito/process/workitem/TaskMetaEntity.java rename to api/kogito-api/src/main/java/org/kie/kogito/usertask/model/UserTaskEntity.java index 49150a0af6a..2b509789075 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/usertask/model/UserTaskEntity.java @@ -16,12 +16,12 @@ * specific language governing permissions and limitations * under the License. */ -package org.kie.kogito.process.workitem; +package org.kie.kogito.usertask.model; import java.io.Serializable; import java.util.Date; -public class TaskMetaEntity implements Serializable, Cloneable { +public class UserTaskEntity implements Serializable, Cloneable { private static final long serialVersionUID = 1L; private K id; @@ -29,10 +29,10 @@ public class TaskMetaEntity impl protected Date updatedAt; protected String updatedBy; - public TaskMetaEntity() { + public UserTaskEntity() { } - public TaskMetaEntity(K id, String user) { + public UserTaskEntity(K id, String user) { this.id = id; this.updatedBy = user; } @@ -81,7 +81,7 @@ public boolean equals(Object obj) { return false; if (getClass() != obj.getClass()) return false; - return id.equals(((TaskMetaEntity) obj).id); + return id.equals(((UserTaskEntity) obj).id); } @Override 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 index 660ea4c8446..6c7c47f3459 100644 --- 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 @@ -33,7 +33,7 @@ public class UserTaskView { private String taskName; private String taskDescription; - private Integer taskPriority; + private String taskPriority; private Set potentialUsers; private Set potentialGroups; private Set adminUsers; @@ -87,11 +87,11 @@ public void setTaskDescription(String taskDescription) { this.taskDescription = taskDescription; } - public Integer getTaskPriority() { + public String getTaskPriority() { return taskPriority; } - public void setTaskPriority(Integer taskPriority) { + public void setTaskPriority(String taskPriority) { this.taskPriority = taskPriority; } diff --git a/api/kogito-events-api/src/main/java/org/kie/kogito/event/usertask/UserTaskInstanceStateEventBody.java b/api/kogito-events-api/src/main/java/org/kie/kogito/event/usertask/UserTaskInstanceStateEventBody.java index a8423c45355..ac4cfa7b9b0 100644 --- a/api/kogito-events-api/src/main/java/org/kie/kogito/event/usertask/UserTaskInstanceStateEventBody.java +++ b/api/kogito-events-api/src/main/java/org/kie/kogito/event/usertask/UserTaskInstanceStateEventBody.java @@ -47,6 +47,8 @@ public class UserTaskInstanceStateEventBody { private String eventType; + private String externalReferenceId; + public Date getEventDate() { return eventDate; } @@ -95,6 +97,10 @@ public String getEventType() { return eventType; } + public String getExternalReferenceId() { + return externalReferenceId; + } + public Map metaData() { Map metadata = new HashMap<>(); metadata.put(UserTaskInstanceEventMetadata.USER_TASK_INSTANCE_ID_META_DATA, userTaskInstanceId); @@ -194,6 +200,11 @@ public Builder actualOwner(String userId) { return this; } + public Builder externalReferenceId(String externalReferenceId) { + this.instance.externalReferenceId = externalReferenceId; + return this; + } + public Builder eventType(String eventType) { this.instance.eventType = eventType; return this; diff --git a/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/AbstractDataEventAdapter.java b/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/AbstractDataEventAdapter.java index 72f1bd16701..77f1c856ba4 100644 --- a/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/AbstractDataEventAdapter.java +++ b/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/AbstractDataEventAdapter.java @@ -108,19 +108,19 @@ protected ProcessInstanceNodeDataEvent toProcessInstanceNodeEvent(ProcessNodeEve .nodeDefinitionId(event.getNodeInstance().getNode().getUniqueId()) .slaDueDate(nodeInstance.getSlaDueDate()); + if (event.getNodeInstance() instanceof KogitoWorkItemNodeInstance workItemNodeInstance && workItemNodeInstance.getWorkItem() != null) { + KogitoWorkItem workItem = workItemNodeInstance.getWorkItem(); + builder.workItemId(workItem.getStringId()); + builder.data("WorkItemId", workItem.getStringId()); + builder.data("WorkItemExternalReferenceId", workItem.getExternalReferenceId()); + } + if (eventType == ProcessInstanceNodeEventBody.EVENT_TYPE_ENTER) { builder.connectionNodeDefinitionId((String) nodeInstance.getMetaData().get("IncomingConnection")); } else { builder.connectionNodeDefinitionId((String) nodeInstance.getMetaData().get("OutgoingConnection")); } - if (nodeInstance instanceof KogitoWorkItemNodeInstance) { - KogitoWorkItem workItem = ((KogitoWorkItemNodeInstance) nodeInstance).getWorkItem(); - if (workItem != null) { - builder.workItemId(workItem.getStringId()); - } - } - ProcessInstanceNodeEventBody body = builder.build(); ProcessInstanceNodeDataEvent piEvent = new ProcessInstanceNodeDataEvent(AdapterHelper.buildSource(getConfig().service(), event.getProcessInstance().getProcessId()), getConfig().addons().toString(), event.getEventIdentity(), metadata, body); diff --git a/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/AdapterHelper.java b/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/AdapterHelper.java index b2897eec6ec..5fe0c4f1c80 100644 --- a/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/AdapterHelper.java +++ b/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/AdapterHelper.java @@ -54,7 +54,7 @@ public static Map buildUserTaskMetadata(UserTaskInstance pi) { metadata.put(ProcessInstanceEventMetadata.ROOT_PROCESS_ID_META_DATA, pi.getMetadata().get("RootProcessId")); metadata.put(ProcessInstanceEventMetadata.ROOT_PROCESS_INSTANCE_ID_META_DATA, pi.getMetadata().get("RootProcessInstanceId")); - metadata.put(UserTaskInstanceEventMetadata.USER_TASK_INSTANCE_ID_META_DATA, pi.getExternalReferenceId()); + metadata.put(UserTaskInstanceEventMetadata.USER_TASK_INSTANCE_ID_META_DATA, pi.getId()); metadata.put(UserTaskInstanceEventMetadata.USER_TASK_INSTANCE_REFERENCE_ID_META_DATA, pi.getUserTask().getReferenceName()); metadata.put(UserTaskInstanceEventMetadata.USER_TASK_INSTANCE_STATE_META_DATA, pi.getStatus().getName()); @@ -69,6 +69,7 @@ public static String buildSource(String service, String processId) { if (processId == null) { return null; } else { + processId = processId.replace(" ", "-"); return service + "/" + (processId.contains(".") ? processId.substring(processId.lastIndexOf('.') + 1) : processId); } } diff --git a/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/ProcessNodeEnteredEventDataEventAdapter.java b/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/ProcessNodeEnteredEventDataEventAdapter.java index f6a048e27c9..44163f2d985 100644 --- a/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/ProcessNodeEnteredEventDataEventAdapter.java +++ b/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/ProcessNodeEnteredEventDataEventAdapter.java @@ -18,40 +18,19 @@ */ package org.kie.kogito.event.impl.adapter; -import org.kie.api.event.process.ProcessNodeLeftEvent; +import org.kie.api.event.process.ProcessNodeTriggeredEvent; import org.kie.kogito.event.DataEvent; import org.kie.kogito.event.process.ProcessInstanceNodeEventBody; -import org.kie.kogito.internal.process.runtime.KogitoNodeInstance; public class ProcessNodeEnteredEventDataEventAdapter extends AbstractDataEventAdapter { public ProcessNodeEnteredEventDataEventAdapter() { - super(ProcessNodeLeftEvent.class); + super(ProcessNodeTriggeredEvent.class); } @Override public DataEvent adapt(Object payload) { - ProcessNodeLeftEvent event = (ProcessNodeLeftEvent) payload; - KogitoNodeInstance nodeInstance = (KogitoNodeInstance) event.getNodeInstance(); - int eventType = ProcessInstanceNodeEventBody.EVENT_TYPE_EXIT; - - if (nodeInstance.getCancelType() != null) { - switch (nodeInstance.getCancelType()) { - case ABORTED: - eventType = ProcessInstanceNodeEventBody.EVENT_TYPE_ABORTED; - break; - case SKIPPED: - eventType = ProcessInstanceNodeEventBody.EVENT_TYPE_SKIPPED; - break; - case OBSOLETE: - eventType = ProcessInstanceNodeEventBody.EVENT_TYPE_OBSOLETE; - break; - case ERROR: - eventType = ProcessInstanceNodeEventBody.EVENT_TYPE_ERROR; - } - } - - return toProcessInstanceNodeEvent((ProcessNodeLeftEvent) payload, eventType); + return toProcessInstanceNodeEvent((ProcessNodeTriggeredEvent) payload, ProcessInstanceNodeEventBody.EVENT_TYPE_ENTER); } } diff --git a/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/ProcessNodeLeftEventDataEventAdapter.java b/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/ProcessNodeLeftEventDataEventAdapter.java index 5ae72fc5b3b..ec5a3dff5ea 100644 --- a/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/ProcessNodeLeftEventDataEventAdapter.java +++ b/api/kogito-events-core/src/main/java/org/kie/kogito/event/impl/adapter/ProcessNodeLeftEventDataEventAdapter.java @@ -18,19 +18,40 @@ */ package org.kie.kogito.event.impl.adapter; -import org.kie.api.event.process.ProcessNodeTriggeredEvent; +import org.kie.api.event.process.ProcessNodeLeftEvent; import org.kie.kogito.event.DataEvent; import org.kie.kogito.event.process.ProcessInstanceNodeEventBody; +import org.kie.kogito.internal.process.runtime.KogitoNodeInstance; public class ProcessNodeLeftEventDataEventAdapter extends AbstractDataEventAdapter { public ProcessNodeLeftEventDataEventAdapter() { - super(ProcessNodeTriggeredEvent.class); + super(ProcessNodeLeftEvent.class); } @Override public DataEvent adapt(Object payload) { - return toProcessInstanceNodeEvent((ProcessNodeTriggeredEvent) payload, ProcessInstanceNodeEventBody.EVENT_TYPE_ENTER); + ProcessNodeLeftEvent event = (ProcessNodeLeftEvent) payload; + KogitoNodeInstance nodeInstance = (KogitoNodeInstance) event.getNodeInstance(); + int eventType = ProcessInstanceNodeEventBody.EVENT_TYPE_EXIT; + + if (nodeInstance.getCancelType() != null) { + switch (nodeInstance.getCancelType()) { + case ABORTED: + eventType = ProcessInstanceNodeEventBody.EVENT_TYPE_ABORTED; + break; + case SKIPPED: + eventType = ProcessInstanceNodeEventBody.EVENT_TYPE_SKIPPED; + break; + case OBSOLETE: + eventType = ProcessInstanceNodeEventBody.EVENT_TYPE_OBSOLETE; + break; + case ERROR: + eventType = ProcessInstanceNodeEventBody.EVENT_TYPE_ERROR; + } + } + + return toProcessInstanceNodeEvent((ProcessNodeLeftEvent) payload, eventType); } } 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 af70b37ae35..68ade25916e 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 @@ -42,8 +42,6 @@ public boolean accept(Object payload) { public DataEvent adapt(Object payload) { UserTaskStateEvent event = (UserTaskStateEvent) payload; Map metadata = AdapterHelper.buildUserTaskMetadata(event.getUserTaskInstance()); - Integer priority = event.getUserTaskInstance().getTaskPriority(); - String priorityStr = priority != null ? priority.toString() : null; UserTaskInstanceStateEventBody.Builder builder = UserTaskInstanceStateEventBody.create() .eventDate(new Date()) @@ -52,8 +50,9 @@ public DataEvent adapt(Object payload) { .userTaskInstanceId(event.getUserTaskInstance().getId()) .userTaskName(event.getUserTaskInstance().getTaskName()) .userTaskDescription(event.getUserTaskInstance().getTaskDescription()) - .userTaskPriority(priorityStr) + .userTaskPriority(event.getUserTaskInstance().getTaskPriority()) .userTaskReferenceName(event.getUserTask().getReferenceName()) + .externalReferenceId(event.getUserTaskInstance().getExternalReferenceId()) .state(event.getNewStatus().getName()) .actualOwner(event.getUserTaskInstance().getActualOwner()) .eventType(isTransition(event) ? event.getNewStatus().getName() : "Modify") diff --git a/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/InMemoryJobService.java b/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/InMemoryJobService.java index 8a139b6c897..c3ea0ad7f86 100644 --- a/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/InMemoryJobService.java +++ b/api/kogito-services/src/main/java/org/kie/kogito/services/jobs/impl/InMemoryJobService.java @@ -53,7 +53,7 @@ public class InMemoryJobService implements JobsService, AutoCloseable { protected ConcurrentHashMap> scheduledJobs = new ConcurrentHashMap<>(); private final Processes processes; - private static final ConcurrentHashMap INSTANCE = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap INSTANCE = new ConcurrentHashMap<>(); protected InMemoryJobService(Processes processes, UnitOfWorkManager unitOfWorkManager) { this(processes, unitOfWorkManager, new ScheduledThreadPoolExecutor(Integer.parseInt(System.getProperty(IN_MEMORY_JOB_SERVICE_POOL_SIZE_PROPERTY, "10")))); @@ -68,14 +68,14 @@ protected InMemoryJobService(Processes processes, UnitOfWorkManager unitOfWorkMa public static InMemoryJobService get(Processes processes, UnitOfWorkManager unitOfWorkManager) { Objects.requireNonNull(processes); Objects.requireNonNull(unitOfWorkManager); - return INSTANCE.computeIfAbsent(processes, k -> new InMemoryJobService(processes, unitOfWorkManager)); + return INSTANCE.computeIfAbsent(processes.hashCode() + 7 * unitOfWorkManager.hashCode(), k -> new InMemoryJobService(processes, unitOfWorkManager)); } public static InMemoryJobService get(Processes processes, UnitOfWorkManager unitOfWorkManager, ScheduledExecutorService scheduler) { Objects.requireNonNull(processes); Objects.requireNonNull(unitOfWorkManager); Objects.requireNonNull(scheduler); - return INSTANCE.computeIfAbsent(processes, k -> new InMemoryJobService(processes, unitOfWorkManager, scheduler)); + return INSTANCE.computeIfAbsent(processes.hashCode() + 7 * unitOfWorkManager.hashCode(), k -> new InMemoryJobService(processes, unitOfWorkManager, scheduler)); } @Override @@ -107,8 +107,12 @@ public String scheduleProcessInstanceJob(ProcessInstanceJobDescription descripti } public Runnable getSignalProcessInstanceCommand(ProcessInstanceJobDescription description, boolean remove, int limit) { - return new SignalProcessInstanceOnExpiredTimer(description.id(), description.timerId(), description - .processInstanceId(), description.processId(), remove, limit); + return new SignalProcessInstanceOnExpiredTimer( + description.id(), + description.timerId(), + description.processInstanceId(), + remove, + limit); } @Override @@ -119,7 +123,10 @@ public boolean cancelJob(String id) { public boolean cancelJob(String id, boolean force) { LOGGER.debug("Cancel Job: {}", id); if (scheduledJobs.containsKey(id)) { - return scheduledJobs.remove(id).cancel(force); + ScheduledFuture future = scheduledJobs.remove(id); + if (!future.isDone()) { + return future.cancel(force); + } } return false; } @@ -147,15 +154,13 @@ private class SignalProcessInstanceOnExpiredTimer implements Runnable { private boolean removeAtExecution; private String processInstanceId; private Integer limit; - private String processId; - private SignalProcessInstanceOnExpiredTimer(String id, String timerId, String processInstanceId, String processId, boolean removeAtExecution, Integer limit) { + private SignalProcessInstanceOnExpiredTimer(String id, String timerId, String processInstanceId, boolean removeAtExecution, Integer limit) { this.id = id; this.timerId = timerId; this.processInstanceId = processInstanceId; this.removeAtExecution = removeAtExecution; this.limit = limit; - this.processId = processId; } @Override @@ -216,12 +221,12 @@ public void run() { }); limit--; if (limit == 0) { - cancelJob(id, false); + cancelJob(id, true); } LOGGER.debug("Job {} completed", id); } finally { if (removeAtExecution) { - cancelJob(id, true); + cancelJob(id, false); } } } diff --git a/jbpm/jbpm-bpmn2/src/main/java/org/jbpm/bpmn2/xml/UserTaskHandler.java b/jbpm/jbpm-bpmn2/src/main/java/org/jbpm/bpmn2/xml/UserTaskHandler.java index f325438516a..9b6b9e6c94d 100644 --- a/jbpm/jbpm-bpmn2/src/main/java/org/jbpm/bpmn2/xml/UserTaskHandler.java +++ b/jbpm/jbpm-bpmn2/src/main/java/org/jbpm/bpmn2/xml/UserTaskHandler.java @@ -20,7 +20,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.Set; import org.jbpm.compiler.xml.Parser; import org.jbpm.process.core.Work; @@ -42,22 +41,6 @@ public Class generateNodeFor() { return HumanTaskNode.class; } - private static final Set taskParameters = Set.of( - "NotStartedNotify", - "NotCompletedNotify", - "NotCompletedReassign", - "NotStartedReassign", - "Description", - "Comment", - "ActorId", - "GroupId", - "Priority", - "Skippable", - "Content", - "ExcludedOwnerId", - "BusinessAdministratorId", - "BusinessAdministratorGroupId"); - @Override protected Node handleNode(final Node node, final Element element, final String uri, final String localName, final Parser parser) throws SAXException { @@ -66,7 +49,7 @@ protected Node handleNode(final Node node, final Element element, final String u Work work = humanTaskNode.getWork(); work.setName("Human Task"); - taskParameters.forEach(p -> setParameter(work, p, humanTaskNode.getIoSpecification().getDataInputAssociation())); + HumanTaskNode.TASK_PARAMETERS.forEach(p -> setParameter(work, p, humanTaskNode.getIoSpecification().getDataInputAssociation())); List owners = new ArrayList<>(); org.w3c.dom.Node xmlNode = element.getFirstChild(); diff --git a/jbpm/jbpm-deps-groups/jbpm-deps-group-bpmn2-compiler/pom.xml b/jbpm/jbpm-deps-groups/jbpm-deps-group-bpmn2-compiler/pom.xml index 12a419d59e8..fc95ee21aa7 100644 --- a/jbpm/jbpm-deps-groups/jbpm-deps-group-bpmn2-compiler/pom.xml +++ b/jbpm/jbpm-deps-groups/jbpm-deps-group-bpmn2-compiler/pom.xml @@ -41,6 +41,10 @@ org.kie.kogito jbpm-usertask + + org.kie.kogito + jbpm-usertask-workitem + diff --git a/jbpm/jbpm-deps-groups/jbpm-deps-group-compiler/pom.xml b/jbpm/jbpm-deps-groups/jbpm-deps-group-compiler/pom.xml index 549fe524212..dc1c82939e6 100644 --- a/jbpm/jbpm-deps-groups/jbpm-deps-group-compiler/pom.xml +++ b/jbpm/jbpm-deps-groups/jbpm-deps-group-compiler/pom.xml @@ -36,6 +36,10 @@ org.kie.kogito jbpm-usertask + + org.kie.kogito + jbpm-usertask-workitem + diff --git a/jbpm/jbpm-deps-groups/jbpm-deps-group-engine/pom.xml b/jbpm/jbpm-deps-groups/jbpm-deps-group-engine/pom.xml index 95c51d1b808..49de8b3f4a1 100644 --- a/jbpm/jbpm-deps-groups/jbpm-deps-group-engine/pom.xml +++ b/jbpm/jbpm-deps-groups/jbpm-deps-group-engine/pom.xml @@ -33,11 +33,14 @@ org.kie.kogito process-workitems - org.kie.kogito jbpm-usertask + + org.kie.kogito + jbpm-usertask-workitem + diff --git a/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/WorkItemModelMetaData.java b/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/WorkItemModelMetaData.java index 5ae74187907..e3f20ed8735 100644 --- a/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/WorkItemModelMetaData.java +++ b/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/compiler/canonical/WorkItemModelMetaData.java @@ -18,9 +18,7 @@ */ package org.jbpm.compiler.canonical; -import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; @@ -30,6 +28,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; @@ -79,9 +78,6 @@ public class WorkItemModelMetaData { private static final String WORK_ITEM = "workItem"; private static final String PARAMS = "params"; - protected static final List INTERNAL_FIELDS = Arrays.asList(TASK_NAME, "NodeName", "ActorId", "GroupId", "Priority", "Comment", "Skippable", "Content", "Locale", - "NotStartedNotify", "NotCompletedNotify", "NotCompletedReassign", "NotStartedReassign"); - private final String packageName; private final VariableScope processVariableScope; @@ -192,7 +188,7 @@ private CompilationUnit compilationUnitInput() { // map is task input -> context variable / process variable Map inputTypes = workItemNode.getIoSpecification().getInputTypes(); for (Entry entry : workItemNode.getIoSpecification().getInputMapping().entrySet()) { - if (INTERNAL_FIELDS.contains(entry.getKey())) { + if (HumanTaskNode.TASK_PARAMETERS.contains(entry.getKey())) { continue; } @@ -237,7 +233,7 @@ private CompilationUnit compilationUnitInput() { for (Entry entry : workItemNode.getWork().getParameters().entrySet()) { - if (entry.getValue() == null || INTERNAL_FIELDS.contains(entry.getKey())) { + if (entry.getValue() == null || HumanTaskNode.TASK_PARAMETERS.contains(entry.getKey())) { continue; } @@ -304,7 +300,7 @@ private CompilationUnit compilationUnitOutput() { // map is task output -> context variable / process variable Map outputTypes = workItemNode.getIoSpecification().getOutputTypes(); for (Entry entry : workItemNode.getIoSpecification().getOutputMappingBySources().entrySet()) { - if (entry.getValue() == null || INTERNAL_FIELDS.contains(entry.getKey())) { + if (entry.getValue() == null || HumanTaskNode.TASK_PARAMETERS.contains(entry.getKey())) { continue; } diff --git a/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/test/util/DefaultCountDownProcessEventListener.java b/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/test/util/DefaultCountDownProcessEventListener.java index 838bc4120ef..b00a151e446 100644 --- a/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/test/util/DefaultCountDownProcessEventListener.java +++ b/jbpm/jbpm-flow-builder/src/main/java/org/jbpm/test/util/DefaultCountDownProcessEventListener.java @@ -48,7 +48,10 @@ public boolean waitTillCompleted(long timeOut) { return latch.await(timeOut, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - logger.debug("Interrputed thread while waiting for all triggers"); + logger.error("Interrputed thread while waiting for all triggers", e); + return false; + } catch (Exception e) { + logger.error("Error during waiting state", e); return false; } } diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/datatype/impl/type/ObjectDataType.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/datatype/impl/type/ObjectDataType.java index 3ed0ab31a84..ce547bec665 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/datatype/impl/type/ObjectDataType.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/process/core/datatype/impl/type/ObjectDataType.java @@ -25,6 +25,7 @@ import org.jbpm.process.core.datatype.DataType; import org.jbpm.process.core.datatype.DataTypeUtils; +import org.jbpm.process.core.datatype.impl.coverter.CloneHelper; import org.jbpm.process.core.datatype.impl.coverter.TypeConverterRegistry; /** @@ -86,6 +87,10 @@ public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(className); } + public Object clone(Object value) { + return CloneHelper.get().clone(value); + } + @Override public boolean verifyDataType(final Object value) { if (value == null) { diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/HumanTaskNode.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/HumanTaskNode.java index d54992d9fe7..872276850ae 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/HumanTaskNode.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/core/node/HumanTaskNode.java @@ -33,6 +33,26 @@ public class HumanTaskNode extends WorkItemNode { private String swimlane; + public static final Set TASK_PARAMETERS = Set.of( + Work.PARAMETER_UNIQUE_TASK_ID, + "TaskName", + "NodeName", + "NotStartedNotify", + "NotCompletedNotify", + "NotCompletedReassign", + "NotStartedReassign", + "Description", + "Comment", + "ActorId", + "GroupId", + "Priority", + "Skippable", + "Content", + "Locale", + "ExcludedOwnerId", + "BusinessAdministratorId", + "BusinessAdministratorGroupId"); + public HumanTaskNode() { Work work = new WorkImpl(); work.setName("Human Task"); diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/CompositeNodeInstance.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/CompositeNodeInstance.java index 2612be7998b..ea9a6774629 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/CompositeNodeInstance.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/CompositeNodeInstance.java @@ -54,7 +54,7 @@ /** * Runtime counterpart of a composite node. - * + * */ public class CompositeNodeInstance extends StateBasedNodeInstance implements NodeInstanceContainer, EventNodeInstanceInterface, EventBasedNodeInstanceInterface { @@ -190,7 +190,7 @@ public void cancel(CancelType cancelType) { @Override public void addNodeInstance(final NodeInstance nodeInstance) { if (nodeInstance.getStringId() == null) { - // assign new id only if it does not exist as it might already be set by marshalling + // assign new id only if it does not exist as it might already be set by marshalling // it's important to keep same ids of node instances as they might be references e.g. exclusive group ((NodeInstanceImpl) nodeInstance).setId(UUID.randomUUID().toString()); } @@ -204,7 +204,7 @@ public void removeNodeInstance(final NodeInstance nodeInstance) { @Override public Collection getNodeInstances() { - return new ArrayList<>(getNodeInstances(false)); + return Collections.unmodifiableCollection(nodeInstances); } @Override diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/FaultNodeInstance.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/FaultNodeInstance.java index 2340848f7b0..ec8fa4b948f 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/FaultNodeInstance.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/FaultNodeInstance.java @@ -88,6 +88,7 @@ public void internalTrigger(KogitoNodeInstance from, String type) { if (getNode().getMetaData().get("hidden") != null) { hidden = true; } + leaveTime = new Date(); if (!hidden) { InternalKnowledgeRuntime kruntime = getProcessInstance().getKnowledgeRuntime(); ((InternalProcessRuntime) kruntime.getProcessRuntime()) diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/ForEachNodeInstance.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/ForEachNodeInstance.java index 339f48d3f2a..9b17022e460 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/ForEachNodeInstance.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/ForEachNodeInstance.java @@ -26,6 +26,8 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import org.jbpm.process.core.ContextContainer; import org.jbpm.process.core.context.variable.VariableScope; @@ -58,6 +60,8 @@ public class ForEachNodeInstance extends CompositeContextNodeInstance { private static final long serialVersionUID = 510L; + private static final Set> NOT_SERIALIZABLE_CLASSES = Set.of(ForEachJoinNodeInstance.class); // using Arrays.asList to allow multiple exclusions + public static final String TEMP_OUTPUT_VAR = "foreach_output"; private int totalInstances; @@ -346,10 +350,25 @@ public ContextInstance getContextInstance(String contextId) { @Override public int getLevelForNode(String uniqueID) { - // always 1 for for each + // always 1 for each return 1; } + @Override + public Collection getSerializableNodeInstances() { + return getNodeInstances().stream().filter(ForEachNodeInstance::isSerializable).collect(Collectors.toUnmodifiableList()); + } + + /** + * Check if the given org.kie.api.runtime.process.NodeInstance is serializable. + * + * @param toCheck + * @return + */ + static boolean isSerializable(org.kie.api.runtime.process.NodeInstance toCheck) { + return !NOT_SERIALIZABLE_CLASSES.contains(toCheck.getClass()); + } + private class ForEachNodeInstanceResolverFactory extends NodeInstanceResolverFactory { private static final long serialVersionUID = -8856846610671009685L; diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/HumanTaskNodeInstance.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/HumanTaskNodeInstance.java index 3dfd858dcb4..6a4616e20a2 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/HumanTaskNodeInstance.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/HumanTaskNodeInstance.java @@ -29,18 +29,9 @@ public class HumanTaskNodeInstance extends WorkItemNodeInstance { private static final long serialVersionUID = 510l; - private static final String NODE_NAME = "NodeName"; - private static final String DESCRIPTION = "Description"; - private static final String PRIORITY = "Priority"; - private static final String TASK_NAME = "TaskName"; private String separator = System.getProperty("org.jbpm.ht.user.separator", ","); - - private static final String ACTUAL_OWNER = "ActualOwner"; private static final String ACTOR_ID = "ActorId"; - private static final String GROUP_ID = "GroupId"; - private static final String BUSINESSADMINISTRATOR_ID = "BusinessAdministratorId"; - private static final String BUSINESSADMINISTRATOR_GROUP_ID = "BusinessAdministratorGroupId"; - private static final String EXCLUDED_OWNER_ID = "ExcludedOwnerId"; + private static final String WORK_ITEM_TRANSITION = "workItemTransition"; private transient SwimlaneContextInstance swimlaneContextInstance; diff --git a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/SplitInstance.java b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/SplitInstance.java index 5d6d2c4d61a..6319299d207 100755 --- a/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/SplitInstance.java +++ b/jbpm/jbpm-flow/src/main/java/org/jbpm/workflow/instance/node/SplitInstance.java @@ -230,6 +230,7 @@ protected void executeStrategy(Split split, String type) { if (getNode().getMetaData().get("hidden") != null) { hidden = true; } + leaveTime = new Date(); InternalKnowledgeRuntime kruntime = getProcessInstance().getKnowledgeRuntime(); if (!hidden) { ((InternalProcessRuntime) kruntime.getProcessRuntime()) 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 99f32900da2..106208afb05 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 @@ -539,7 +539,7 @@ private WorkItem toBaseWorkItem(WorkItemNodeInstance workItemNodeInstance) { workItem.getPhaseStatus(), workItem.getParameters(), workItem.getResults(), - workItem.getParameter(KogitoWorkItem.PARAMETER_UNIQUE_TASK_ID) + ":" + workItem.getExternalReferenceId()); + workItem.getExternalReferenceId()); } private boolean enforce(KogitoWorkItem kogitoWorkItem, Policy... policies) { diff --git a/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/ProcessServiceImpl.java b/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/ProcessServiceImpl.java index 57ad61d3639..1304c6638f2 100644 --- a/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/ProcessServiceImpl.java +++ b/jbpm/jbpm-flow/src/main/java/org/kie/kogito/process/impl/ProcessServiceImpl.java @@ -18,12 +18,9 @@ */ package org.kie.kogito.process.impl; -import java.util.Collection; -import java.util.Date; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.UUID; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -36,7 +33,6 @@ import org.kie.kogito.MapOutput; import org.kie.kogito.MappableToModel; import org.kie.kogito.Model; -import org.kie.kogito.auth.SecurityPolicy; import org.kie.kogito.config.ConfigBean; import org.kie.kogito.correlation.CompositeCorrelation; import org.kie.kogito.internal.process.runtime.KogitoNode; @@ -50,12 +46,6 @@ import org.kie.kogito.process.ProcessService; import org.kie.kogito.process.WorkItem; import org.kie.kogito.services.uow.UnitOfWorkExecutor; -import org.kie.kogito.usertask.UserTask; -import org.kie.kogito.usertask.UserTaskInstance; -import org.kie.kogito.usertask.UserTasks; -import org.kie.kogito.usertask.model.Attachment; -import org.kie.kogito.usertask.model.AttachmentInfo; -import org.kie.kogito.usertask.model.Comment; import static java.util.Collections.emptyMap; @@ -288,183 +278,4 @@ public Map getWorkItemSchemaAndPhases(Process< JsonSchemaUtil.load(Thread.currentThread().getContextClassLoader(), process.id(), workItemTaskName)); } - @Override - public Optional addComment(Process process, - String id, - String taskId, - SecurityPolicy policy, - String commentInfo) { - return updateUserTaskInstance(process, id, taskId, policy, userTaskInstance -> { - Comment comment = new Comment(UUID.randomUUID().toString(), policy.getUser()); - comment.setContent(commentInfo); - comment.setUpdatedAt(new Date()); - userTaskInstance.addComment(comment); - return comment; - }); - - } - - @Override - public Optional updateComment(Process process, - String id, - String taskId, - String commentId, - SecurityPolicy policy, - String commentInfo) { - return updateUserTaskInstance(process, id, taskId, policy, userTaskInstance -> { - Comment comment = new Comment(commentId, policy.getUser()); - comment.setContent(commentInfo); - comment.setUpdatedAt(new Date()); - userTaskInstance.updateComment(comment); - return comment; - }); - } - - @Override - public Optional deleteComment(Process process, - String id, - String taskId, - String commentId, - SecurityPolicy policy) { - return updateUserTaskInstance(process, id, taskId, policy, userTaskInstance -> { - Comment comment = new Comment(commentId, policy.getUser()); - comment.setContent(null); - comment.setUpdatedAt(new Date()); - userTaskInstance.removeComment(comment); - return Boolean.TRUE; - }); - } - - @Override - public Optional addAttachment(Process process, - String id, - String taskId, - SecurityPolicy policy, - AttachmentInfo attachmentInfo) { - return updateUserTaskInstance(process, id, taskId, policy, userTaskInstance -> { - Attachment attachment = new Attachment(UUID.randomUUID().toString(), policy.getUser()); - attachment.setName(attachmentInfo.getName()); - attachment.setContent(attachmentInfo.getUri()); - attachment.setUpdatedAt(new Date()); - userTaskInstance.addAttachment(attachment); - return attachment; - }); - } - - @Override - public Optional updateAttachment(Process process, - String id, - String taskId, - String attachmentId, - SecurityPolicy policy, - AttachmentInfo attachmentInfo) { - return updateUserTaskInstance(process, id, taskId, policy, userTaskInstance -> { - Attachment attachment = new Attachment(attachmentId, policy.getUser()); - attachment.setName(attachmentInfo.getName()); - attachment.setContent(attachmentInfo.getUri()); - attachment.setUpdatedAt(new Date()); - userTaskInstance.updateAttachment(attachment); - return attachment; - }); - } - - @Override - public Optional deleteAttachment(Process process, - String id, - String taskId, - String attachmentId, - SecurityPolicy policy) { - return updateUserTaskInstance(process, id, taskId, policy, userTaskInstance -> { - Attachment attachment = new Attachment(attachmentId, policy.getUser()); - userTaskInstance.removeAttachment(attachment); - return Boolean.TRUE; - }); - } - - private Optional updateUserTaskInstance(Process process, - String id, - String taskId, - SecurityPolicy policy, - Function consumer) { - return UnitOfWorkExecutor.executeInUnitOfWork( - application.unitOfWorkManager(), () -> { - ProcessInstance processInstance = process.instances().findById(id).orElseThrow(() -> new ProcessInstanceNotFoundException(id)); - WorkItem wi = processInstance.workItem(taskId); - String referenceId = wi.getExternalReferenceId(); - if (referenceId == null) { - return Optional.empty(); - } - String[] data = referenceId.split(":"); - UserTasks userTasks = application.get(UserTasks.class); - UserTask userTask = userTasks.userTaskById(data[0]); - Optional instance = userTask.instances().findById(data[0]); - if (instance.isEmpty()) { - return Optional.empty(); - } - UserTaskInstance userTaskInstance = instance.get(); - R outcome = consumer.apply(userTaskInstance); - userTask.instances().update(userTaskInstance); - return Optional.ofNullable(outcome); - }); - } - - @Override - public Optional getAttachment(Process process, - String id, - String taskId, - String attachmentId, - SecurityPolicy policy) { - return retrieveUserTaskInstance(process, id, taskId, policy, ut -> ut.findAttachmentById(attachmentId)); - } - - @Override - public Optional> getAttachments(Process process, - String id, - String taskId, - SecurityPolicy policy) { - return retrieveUserTaskInstance(process, id, taskId, policy, ut -> ut.getAttachments()); - } - - @Override - public Optional getComment(Process process, - String id, - String taskId, - String commentId, - SecurityPolicy policy) { - return retrieveUserTaskInstance(process, id, taskId, policy, ut -> ut.findCommentById(commentId)); - } - - @Override - public Optional> getComments(Process process, - String id, - String taskId, - SecurityPolicy policy) { - return retrieveUserTaskInstance(process, id, taskId, policy, ut -> ut.getComments()); - } - - private Optional retrieveUserTaskInstance(Process process, - String id, - String taskId, - SecurityPolicy policy, - Function consumer) { - return UnitOfWorkExecutor.executeInUnitOfWork( - application.unitOfWorkManager(), () -> { - ProcessInstance processInstance = process.instances().findById(id).orElseThrow(() -> new ProcessInstanceNotFoundException(id)); - WorkItem wi = processInstance.workItem(taskId); - String referenceId = wi.getExternalReferenceId(); - if (referenceId == null) { - return Optional.empty(); - } - String[] data = referenceId.split(":"); - UserTasks userTasks = application.get(UserTasks.class); - UserTask userTask = userTasks.userTaskById(data[0]); - Optional instance = userTask.instances().findById(data[0]); - if (instance.isEmpty()) { - return Optional.empty(); - } - UserTaskInstance userTaskInstance = instance.get(); - R outcome = consumer.apply(userTaskInstance); - return Optional.ofNullable(outcome); - }); - } } diff --git a/jbpm/jbpm-flow/src/test/java/org/jbpm/workflow/instance/node/ForEachNodeInstanceTest.java b/jbpm/jbpm-flow/src/test/java/org/jbpm/workflow/instance/node/ForEachNodeInstanceTest.java new file mode 100644 index 00000000000..23194d30272 --- /dev/null +++ b/jbpm/jbpm-flow/src/test/java/org/jbpm/workflow/instance/node/ForEachNodeInstanceTest.java @@ -0,0 +1,75 @@ +/* + * 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.jbpm.workflow.instance.node; + +import java.util.Collection; + +import org.junit.jupiter.api.Test; +import org.kie.api.runtime.process.NodeInstance; + +import static org.assertj.core.api.Assertions.assertThat; + +class ForEachNodeInstanceTest { + + @Test + void getNodeInstances() { + ForEachNodeInstance toTest = new ForEachNodeInstance(); + CompositeNodeInstance compositeNodeInstance = new CompositeNodeInstance(); + toTest.addNodeInstance(compositeNodeInstance); + Collection nodeInstances = toTest.getNodeInstances(); + assertThat(nodeInstances) + .isNotNull() + .hasSize(1) + .contains(compositeNodeInstance); + ForEachNodeInstance.ForEachJoinNodeInstance forEachJoinNodeInstance = toTest.new ForEachJoinNodeInstance(); + toTest.addNodeInstance(forEachJoinNodeInstance); + nodeInstances = toTest.getNodeInstances(); + assertThat(nodeInstances) + .isNotNull() + .hasSize(2) + .contains(compositeNodeInstance, forEachJoinNodeInstance); + } + + @Test + void getSerializableNodeInstances() { + ForEachNodeInstance toTest = new ForEachNodeInstance(); + CompositeNodeInstance compositeNodeInstance = new CompositeNodeInstance(); + toTest.addNodeInstance(compositeNodeInstance); + Collection serializableNodeInstances = toTest.getSerializableNodeInstances(); + assertThat(serializableNodeInstances) + .isNotNull() + .hasSize(1) + .contains(compositeNodeInstance); + ForEachNodeInstance.ForEachJoinNodeInstance forEachJoinNodeInstance = toTest.new ForEachJoinNodeInstance(); + toTest.addNodeInstance(forEachJoinNodeInstance); + serializableNodeInstances = toTest.getSerializableNodeInstances(); + assertThat(serializableNodeInstances) + .isNotNull() + .hasSize(1) + .contains(compositeNodeInstance); + } + + @Test + void isSerializable() { + assertThat(ForEachNodeInstance.isSerializable(new CompositeNodeInstance())).isTrue(); + assertThat(ForEachNodeInstance.isSerializable(new ForEachNodeInstance())).isTrue(); + assertThat(ForEachNodeInstance.isSerializable(new ForEachNodeInstance().new ForEachJoinNodeInstance())).isFalse(); + } + +} diff --git a/jbpm/jbpm-tests/pom.xml b/jbpm/jbpm-tests/pom.xml index 230f0cd2c4f..4683c28a856 100644 --- a/jbpm/jbpm-tests/pom.xml +++ b/jbpm/jbpm-tests/pom.xml @@ -177,8 +177,6 @@ tests --> true - 2 - all diff --git a/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/IntermediateEventTest.java b/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/IntermediateEventTest.java index 09410070f55..9d2ec7cc3b5 100755 --- a/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/IntermediateEventTest.java +++ b/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/IntermediateEventTest.java @@ -482,7 +482,7 @@ public void testEventBasedSplitAfter() { @Test public void testEventBasedSplit2() { - ProcessCompletedCountDownProcessEventListener countDownListener = new ProcessCompletedCountDownProcessEventListener(2); + ProcessCompletedCountDownProcessEventListener countDownListener = new ProcessCompletedCountDownProcessEventListener(1); Application app = ProcessTestHelper.newApplication(); ProcessTestHelper.registerProcessEventListener(app, countDownListener); ProcessTestHelper.registerHandler(app, "Email1", new SystemOutWorkItemHandler()); @@ -506,6 +506,7 @@ public void testEventBasedSplit2() { instance.send(Sig.of("Yes", "YesValue")); assertThat(instance.status()).isEqualTo(org.kie.kogito.process.ProcessInstance.STATE_COMPLETED); + countDownListener.reset(1); instance = processDefinition.createInstance(model); instance.start(); diff --git a/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/StandaloneBPMNProcessTest.java b/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/StandaloneBPMNProcessTest.java index 78088212334..de8df91a30c 100755 --- a/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/StandaloneBPMNProcessTest.java +++ b/jbpm/jbpm-tests/src/test/java/org/jbpm/bpmn2/StandaloneBPMNProcessTest.java @@ -117,7 +117,6 @@ import org.jbpm.test.utils.ProcessTestHelper; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout; import org.kie.api.event.process.ProcessStartedEvent; import org.kie.api.io.Resource; import org.kie.internal.io.ResourceFactory; @@ -384,7 +383,6 @@ public void testEventBasedSplitAfter() { } @Test - @Timeout(10) public void testEventBasedSplit2() throws Exception { ProcessCompletedCountDownProcessEventListener countDownListener = new ProcessCompletedCountDownProcessEventListener(2); Application app = ProcessTestHelper.newApplication(); @@ -402,7 +400,7 @@ public void testEventBasedSplit2() throws Exception { org.kie.kogito.process.ProcessInstance instanceTimer = processDefinition.createInstance(modelTimer); instanceTimer.start(); assertThat(instanceTimer.status()).isEqualTo(org.kie.kogito.process.ProcessInstance.STATE_ACTIVE); - countDownListener.waitTillCompleted(); + assertThat(countDownListener.waitTillCompleted(15000)).isTrue(); assertThat(instanceYes.status()).isEqualTo(org.kie.kogito.process.ProcessInstance.STATE_COMPLETED); assertThat(instanceTimer.status()).isEqualTo(org.kie.kogito.process.ProcessInstance.STATE_COMPLETED); } @@ -537,7 +535,6 @@ public void testErrorBoundaryEvent() throws Exception { } @Test - @Timeout(10) public void testTimerBoundaryEvent() throws Exception { Application app = ProcessTestHelper.newApplication(); NodeLeftCountDownProcessEventListener countDownListener = new NodeLeftCountDownProcessEventListener("TimerEvent", 1); @@ -556,7 +553,6 @@ public void testTimerBoundaryEvent() throws Exception { } @Test - @Timeout(10) public void testTimerBoundaryEventInterrupting() { Application app = ProcessTestHelper.newApplication(); NodeLeftCountDownProcessEventListener countDownListener = new NodeLeftCountDownProcessEventListener("TimerEvent", 1); @@ -697,7 +693,6 @@ public void testIntermediateCatchEventMessage() { } @Test - @Timeout(10) public void testIntermediateCatchEventTimer() { Application app = ProcessTestHelper.newApplication(); NodeLeftCountDownProcessEventListener countDownListener = new NodeLeftCountDownProcessEventListener("timer", 1); @@ -786,7 +781,6 @@ public void testConditionalStart() throws Exception { } @Test - @Timeout(1000) public void testTimerStart() throws Exception { Application app = ProcessTestHelper.newApplication(); NodeLeftCountDownProcessEventListener countDownListener = new NodeLeftCountDownProcessEventListener("StartProcess", 5); diff --git a/jbpm/jbpm-usertask-workitem/pom.xml b/jbpm/jbpm-usertask-workitem/pom.xml new file mode 100644 index 00000000000..f00c773f32a --- /dev/null +++ b/jbpm/jbpm-usertask-workitem/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + jbpm + org.kie.kogito + 999-SNAPSHOT + + + jbpm-usertask-workitem + + + Kogito :: jBPM :: User Task WorkItem + jBPM User Task WorkItem Handler + + + UTF-8 + org.kie.kogito.jbpm.usertask.workitem + + + + + org.kie.kogito + kogito-api + + + org.kie.kogito + jbpm-flow + + + org.kie.kogito + jbpm-usertask + + + org.kie.kogito + process-workitems + + + + + + + org.kie.kogito + kogito-kie-bom + ${project.version} + pom + import + + + + diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/Policies.java b/jbpm/jbpm-usertask-workitem/src/main/java/org/kie/kogito/jbpm/usertask/handler/Policies.java similarity index 100% rename from jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/Policies.java rename to jbpm/jbpm-usertask-workitem/src/main/java/org/kie/kogito/jbpm/usertask/handler/Policies.java diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandler.java b/jbpm/jbpm-usertask-workitem/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandler.java similarity index 95% rename from jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandler.java rename to jbpm/jbpm-usertask-workitem/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandler.java index d612a692961..71d34053787 100644 --- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandler.java +++ b/jbpm/jbpm-usertask-workitem/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandler.java @@ -22,6 +22,7 @@ import java.util.Optional; import java.util.Set; +import org.jbpm.workflow.core.node.HumanTaskNode; import org.kie.kogito.Application; import org.kie.kogito.auth.IdentityProviders; import org.kie.kogito.internal.process.workitem.KogitoWorkItem; @@ -69,19 +70,12 @@ public Optional activateWorkItemHandler(KogitoWorkItemManage UserTasks userTasks = handler.getApplication().get(UserTasks.class); Object priority = workItem.getParameter(PRIORITY); - Integer priorityInteger = null; - if (priority instanceof String priorityString) { - priorityInteger = Integer.parseInt((String) priorityString); - } else { - priority = (Integer) priority; - } - UserTask userTask = userTasks.userTaskById((String) workItem.getParameter(KogitoWorkItem.PARAMETER_UNIQUE_TASK_ID)); DefaultUserTaskInstance instance = (DefaultUserTaskInstance) userTask.createInstance(); instance.setTaskName((String) workItem.getParameter(TASK_NAME)); instance.setTaskDescription((String) workItem.getParameter(DESCRIPTION)); - instance.setTaskPriority(priorityInteger); + instance.setTaskPriority(priority != null ? priority.toString() : null); instance.setExternalReferenceId(workItem.getStringId()); instance.setMetadata("ProcessId", workItem.getProcessInstance().getProcessId()); @@ -95,7 +89,7 @@ public Optional activateWorkItemHandler(KogitoWorkItemManage userTask.instances().create(instance); instance.fireInitialStateChange(); - workItem.getParameters().forEach(instance::setInput); + workItem.getParameters().entrySet().stream().filter(e -> !HumanTaskNode.TASK_PARAMETERS.contains(e.getKey())).forEach(e -> instance.setInput(e.getKey(), e.getValue())); 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); diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandlerFactory.java b/jbpm/jbpm-usertask-workitem/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandlerFactory.java similarity index 100% rename from jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandlerFactory.java rename to jbpm/jbpm-usertask-workitem/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandlerFactory.java diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandlerProcessListener.java b/jbpm/jbpm-usertask-workitem/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandlerProcessListener.java similarity index 100% rename from jbpm/jbpm-usertask/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandlerProcessListener.java rename to jbpm/jbpm-usertask-workitem/src/main/java/org/kie/kogito/jbpm/usertask/handler/UserTaskKogitoWorkItemHandlerProcessListener.java diff --git a/jbpm/jbpm-usertask/src/main/resources/META-INF/services/org.kie.kogito.internal.process.workitem.KogitoWorkItemHandlerFactory b/jbpm/jbpm-usertask-workitem/src/main/resources/META-INF/services/org.kie.kogito.internal.process.workitem.KogitoWorkItemHandlerFactory similarity index 100% rename from jbpm/jbpm-usertask/src/main/resources/META-INF/services/org.kie.kogito.internal.process.workitem.KogitoWorkItemHandlerFactory rename to jbpm/jbpm-usertask-workitem/src/main/resources/META-INF/services/org.kie.kogito.internal.process.workitem.KogitoWorkItemHandlerFactory diff --git a/jbpm/jbpm-usertask/pom.xml b/jbpm/jbpm-usertask/pom.xml index 3adb16a9ff0..29975eebb3f 100644 --- a/jbpm/jbpm-usertask/pom.xml +++ b/jbpm/jbpm-usertask/pom.xml @@ -25,10 +25,6 @@ org.kie.kogito kogito-api - - org.kie.kogito - process-workitems - org.kie.kogito kogito-services 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 index 19a7d8c1e74..68f85aceb3f 100644 --- 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 @@ -37,7 +37,7 @@ public abstract class AbstractUserTask implements UserTask { private String taskName; private String taskDescription; private String referenceName; - private Integer taskPriority; + private String taskPriority; private Boolean skippable; private Set potentialUsers; private Set potentialGroups; @@ -116,11 +116,11 @@ public void setTaskDescription(String taskDescription) { } @Override - public Integer getTaskPriority() { + public String getTaskPriority() { return this.taskPriority; } - public void setTaskPriority(Integer taskPriority) { + public void setTaskPriority(String taskPriority) { this.taskPriority = taskPriority; } 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 e5265ae3f7f..fd6d3ce21c3 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 @@ -52,7 +52,7 @@ public class DefaultUserTaskInstance implements UserTaskInstance { private String actualOwner; private String taskName; private String taskDescription; - private Integer taskPriority; + private String taskPriority; private Set potentialUsers; private Set potentialGroups; private Set adminUsers; @@ -282,11 +282,11 @@ public void setTaskDescription(String taskDescription) { * @return task priority if present */ @Override - public Integer getTaskPriority() { + public String getTaskPriority() { return this.taskPriority; } - public void setTaskPriority(Integer taskPriority) { + public void setTaskPriority(String taskPriority) { this.taskPriority = taskPriority; if (this.userTaskEventSupport != null) { this.userTaskEventSupport.fireOneUserTaskStateChange(this, this.status, this.status); 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 index da83176f6d8..3f1009245ec 100644 --- 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 @@ -73,6 +73,7 @@ private UserTaskView toUserTaskView(UserTaskInstance instance) { view.setInputs(instance.getInputs()); view.setOutputs(instance.getOutputs()); view.setMetadata(instance.getMetadata()); + view.setExternalReferenceId(instance.getExternalReferenceId()); return view; } 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 369c7fc6bca..a28a5accfde 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 @@ -26,9 +26,9 @@ 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.UserTaskInstanceNotAuthorizedException; import org.kie.kogito.usertask.impl.DefaultUserTaskInstance; import org.kie.kogito.usertask.lifecycle.UserTaskLifeCycle; import org.kie.kogito.usertask.lifecycle.UserTaskState; @@ -205,7 +205,7 @@ private void checkPermission(UserTaskInstance userTaskInstance, IdentityProvider } } - throw new NotAuthorizedException("user " + user + " with roles " + roles + " not autorized to perform an operation on user task " + userTaskInstance.getId()); + throw new UserTaskInstanceNotAuthorizedException("user " + user + " with roles " + roles + " not autorized to perform an operation on user task " + userTaskInstance.getId()); } } diff --git a/jbpm/pom.xml b/jbpm/pom.xml index b53bf127c7c..92f53bb73da 100755 --- a/jbpm/pom.xml +++ b/jbpm/pom.xml @@ -47,6 +47,7 @@ process-serialization-protobuf process-workitems jbpm-usertask + jbpm-usertask-workitem jbpm-tools jbpm-deps-groups diff --git a/jbpm/process-serialization-protobuf/src/main/java/org/jbpm/flow/serialization/impl/ProtobufProcessInstanceWriter.java b/jbpm/process-serialization-protobuf/src/main/java/org/jbpm/flow/serialization/impl/ProtobufProcessInstanceWriter.java index b0f67430dbb..700d7abd422 100644 --- a/jbpm/process-serialization-protobuf/src/main/java/org/jbpm/flow/serialization/impl/ProtobufProcessInstanceWriter.java +++ b/jbpm/process-serialization-protobuf/src/main/java/org/jbpm/flow/serialization/impl/ProtobufProcessInstanceWriter.java @@ -175,7 +175,7 @@ private List buildSwimlaneContexts(Swimlane return contexts; } - private KogitoTypesProtobuf.WorkflowContext buildWorkflowContext(List nodeInstances, + protected KogitoTypesProtobuf.WorkflowContext buildWorkflowContext(List nodeInstances, List exclusiveGroupInstances, List> variables, List> iterationlevels) { @@ -248,8 +248,8 @@ public FieldDescriptor getContextField(GeneratedMessageV3.Builder builder) { return null; } - private WorkflowContext buildWorkflowContext(T nodeInstance) { - List nodeInstances = new ArrayList<>(nodeInstance.getNodeInstances()); + protected WorkflowContext buildWorkflowContext(T nodeInstance) { + List nodeInstances = new ArrayList<>(nodeInstance.getSerializableNodeInstances()); List exclusiveGroupInstances = nodeInstance.getContextInstances(ExclusiveGroup.EXCLUSIVE_GROUP); VariableScopeInstance variableScopeInstance = (VariableScopeInstance) nodeInstance.getContextInstance(VariableScope.VARIABLE_SCOPE); List> variables = (variableScopeInstance != null) ? new ArrayList<>(variableScopeInstance.getVariables().entrySet()) : Collections.emptyList(); diff --git a/jbpm/process-serialization-protobuf/src/main/java/org/jbpm/flow/serialization/impl/marshallers/ProtobufJsonNodeMessageMarshaller.java b/jbpm/process-serialization-protobuf/src/main/java/org/jbpm/flow/serialization/impl/marshallers/ProtobufJsonNodeMessageMarshaller.java index ce7f6d16cf7..afa5bbcd102 100644 --- a/jbpm/process-serialization-protobuf/src/main/java/org/jbpm/flow/serialization/impl/marshallers/ProtobufJsonNodeMessageMarshaller.java +++ b/jbpm/process-serialization-protobuf/src/main/java/org/jbpm/flow/serialization/impl/marshallers/ProtobufJsonNodeMessageMarshaller.java @@ -52,7 +52,7 @@ public Any marshall(Object unmarshalled) { public Object unmarshall(Any data) { try { KogitoTypesProtobuf.JsonNode storedValue = data.unpack(KogitoTypesProtobuf.JsonNode.class); - return ObjectMapperFactory.get().readTree(storedValue.getContent()); + return ObjectMapperFactory.listenerAware().readTree(storedValue.getContent()); } catch (InvalidProtocolBufferException | JsonProcessingException e1) { throw new ProcessInstanceMarshallerException("Error trying to unmarshalling a Json Node value", e1); } diff --git a/jbpm/process-serialization-protobuf/src/test/java/org/jbpm/flow/serialization/ProcessInstanceMarshallTest.java b/jbpm/process-serialization-protobuf/src/test/java/org/jbpm/flow/serialization/ProcessInstanceMarshallTest.java index 427311a1eaa..1ec0925ad8e 100644 --- a/jbpm/process-serialization-protobuf/src/test/java/org/jbpm/flow/serialization/ProcessInstanceMarshallTest.java +++ b/jbpm/process-serialization-protobuf/src/test/java/org/jbpm/flow/serialization/ProcessInstanceMarshallTest.java @@ -67,11 +67,10 @@ import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.NullSource; import org.kie.kogito.internal.process.runtime.KogitoProcessRuntime; +import org.kie.kogito.jackson.utils.ObjectMapperFactory; import org.kie.kogito.process.impl.AbstractProcess; import org.w3c.dom.Document; -import com.fasterxml.jackson.databind.ObjectMapper; - import jakarta.xml.bind.JAXBContext; import jakarta.xml.bind.annotation.XmlRootElement; @@ -197,8 +196,8 @@ private static Stream testRoundTrip() throws Exception { Arguments.of(5l), Arguments.of(BigDecimal.valueOf(10l)), Arguments.of(new MarshableObject("henry")), - Arguments.of(new ObjectMapper().readTree("{ \"key\" : \"value\" }")), - Arguments.of(new ObjectMapper().valueToTree(marshableObject)), + Arguments.of(ObjectMapperFactory.listenerAware().readTree("{ \"key\" : \"value\" }")), + Arguments.of(ObjectMapperFactory.listenerAware().valueToTree(marshableObject)), Arguments.of(new Date()), Arguments.of(Instant.now()), Arguments.of(OffsetDateTime.now()), diff --git a/jbpm/process-serialization-protobuf/src/test/java/org/jbpm/flow/serialization/impl/ProtobufProcessInstanceWriterTest.java b/jbpm/process-serialization-protobuf/src/test/java/org/jbpm/flow/serialization/impl/ProtobufProcessInstanceWriterTest.java new file mode 100644 index 00000000000..4d37ade30f2 --- /dev/null +++ b/jbpm/process-serialization-protobuf/src/test/java/org/jbpm/flow/serialization/impl/ProtobufProcessInstanceWriterTest.java @@ -0,0 +1,149 @@ +/* + * 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.jbpm.flow.serialization.impl; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +import org.jbpm.flow.serialization.MarshallerContextName; +import org.jbpm.flow.serialization.NodeInstanceWriter; +import org.jbpm.flow.serialization.ObjectMarshallerStrategyHelper; +import org.jbpm.process.core.context.variable.VariableScope; +import org.jbpm.process.instance.ContextInstance; +import org.jbpm.process.instance.context.variable.VariableScopeInstance; +import org.jbpm.ruleflow.core.WorkflowElementIdentifierFactory; +import org.jbpm.util.JbpmClassLoaderUtil; +import org.jbpm.workflow.core.impl.WorkflowProcessImpl; +import org.jbpm.workflow.instance.node.ForEachNodeInstance; +import org.jbpm.workflow.instance.node.HumanTaskNodeInstance; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.kie.api.definition.process.WorkflowElementIdentifier; +import org.kie.api.runtime.process.NodeInstance; +import org.kie.kogito.internal.process.runtime.KogitoProcessRuntime; +import org.kie.kogito.process.impl.AbstractProcess; +import org.mockito.ArgumentCaptor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class ProtobufProcessInstanceWriterTest { + + private static AbstractProcess mockProcess; + private static NodeInstanceWriter[] nodeInstanceWriters; + private ProtobufProcessMarshallerWriteContext writeContext; + + @BeforeAll + static void setup() { + ServiceLoader writerLoader = ServiceLoader.load(NodeInstanceWriter.class, JbpmClassLoaderUtil.findClassLoader()); + int items = (int) writerLoader.stream().count(); + nodeInstanceWriters = writerLoader.stream().map(ServiceLoader.Provider::get).map(NodeInstanceWriter.class::cast).toArray(value -> new NodeInstanceWriter[items]); + mockProcess = getMockedProcess(); + } + + @BeforeEach + void init() { + writeContext = new ProtobufProcessMarshallerWriteContext(new ByteArrayOutputStream()); + writeContext.set(MarshallerContextName.OBJECT_MARSHALLING_STRATEGIES, ObjectMarshallerStrategyHelper.defaultStrategies()); + writeContext.set(MarshallerContextName.MARSHALLER_PROCESS, mockProcess); + writeContext.set(MarshallerContextName.MARSHALLER_NODE_INSTANCE_WRITER, nodeInstanceWriters); + } + + @SuppressWarnings("unchecked") + @Test + void buildWorkflowContext() { + ProtobufProcessInstanceWriter spiedProtobufProcessInstanceWriter = spy(new ProtobufProcessInstanceWriter(writeContext)); + ForEachNodeInstance nodeInstance = getNodeInstanceContainer(); + try { + spiedProtobufProcessInstanceWriter.buildWorkflowContext(nodeInstance); + } catch (Exception e) { + // expected due to partial instantiation + assertThat(e).isInstanceOf(NullPointerException.class); + ArgumentCaptor> nodeInstancesCapture = ArgumentCaptor.forClass(ArrayList.class); + ArgumentCaptor> exclusiveGroupInstancesCapture = ArgumentCaptor.forClass(ArrayList.class); + ArgumentCaptor>> variablesCapture = ArgumentCaptor.forClass(ArrayList.class); + ArgumentCaptor>> iterationlevelsCapture = ArgumentCaptor.forClass(ArrayList.class); + verify(spiedProtobufProcessInstanceWriter).buildWorkflowContext(nodeInstancesCapture.capture(), exclusiveGroupInstancesCapture.capture(), variablesCapture.capture(), + iterationlevelsCapture.capture()); + Collection expected = nodeInstance.getSerializableNodeInstances(); + List retrieved = nodeInstancesCapture.getValue(); + assertThat(retrieved).isNotNull().hasSize(expected.size()).allMatch(expected::contains); + } + } + + private ForEachNodeInstance getNodeInstanceContainer() { + String id = "NodeInstanceContainer"; + ForEachNodeInstance toReturn = new ForEachNodeInstance(); + toReturn.setId(id); + toReturn.setLevel(1); + toReturn.addNodeInstance(getNodeInstanceSerializable(id)); + toReturn.addNodeInstance(getNodeInstanceNotSerializable(id)); + toReturn.setNodeId(getWorkflowElementIdentifier(id)); + toReturn.setContextInstance(VariableScope.VARIABLE_SCOPE, new VariableScopeInstance()); + Collection nodeInstances = toReturn.getNodeInstances(); + assertThat(nodeInstances) + .isNotNull() + .hasSize(2) + .anyMatch(HumanTaskNodeInstance.class::isInstance) + .anyMatch(ForEachNodeInstance.ForEachJoinNodeInstance.class::isInstance); + Collection serializableNodeInstances = toReturn.getSerializableNodeInstances(); + assertThat(serializableNodeInstances) + .isNotNull() + .hasSize(1) + .allMatch(HumanTaskNodeInstance.class::isInstance); + return toReturn; + } + + private ForEachNodeInstance.ForEachJoinNodeInstance getNodeInstanceNotSerializable(String parent) { + String id = String.format("%s-%s", parent, "nestedNodeInstanceNotSerializable"); + ForEachNodeInstance.ForEachJoinNodeInstance toReturn = new ForEachNodeInstance().new ForEachJoinNodeInstance(); + toReturn.setId(id); + toReturn.setLevel(2); + return toReturn; + } + + private HumanTaskNodeInstance getNodeInstanceSerializable(String parent) { + String id = String.format("%s-%s", parent, "nestedNodeInstanceSerializable"); + HumanTaskNodeInstance toReturn = new HumanTaskNodeInstance(); + toReturn.setId(id); + toReturn.setNodeId(getWorkflowElementIdentifier(id)); + return toReturn; + } + + private WorkflowElementIdentifier getWorkflowElementIdentifier(String parent) { + String id = String.format("%s-%s", parent, "workflowElementIdentifier"); + return WorkflowElementIdentifierFactory.fromExternalFormat(id); + } + + private static AbstractProcess getMockedProcess() { + AbstractProcess toReturn = mock(AbstractProcess.class); + when(toReturn.getProcessRuntime()).thenReturn(mock(KogitoProcessRuntime.class)); + when(toReturn.get()).thenReturn(new WorkflowProcessImpl()); + return toReturn; + } + +} diff --git a/kogito-bom/pom.xml b/kogito-bom/pom.xml index bc9fba78c45..6e4af07c46d 100755 --- a/kogito-bom/pom.xml +++ b/kogito-bom/pom.xml @@ -1898,6 +1898,11 @@ jbpm-usertask ${project.version} + + org.kie.kogito + jbpm-usertask-workitem + ${project.version} + org.kie.kogito jbpm-flow-migration 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 7a0e493fa4f..150c3d62bab 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 @@ -47,9 +47,9 @@ 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.UserTaskInstanceNotAuthorizedException; import org.kie.kogito.usertask.UserTasks; import org.kie.kogito.usertask.impl.lifecycle.DefaultUserTaskLifeCycle; @@ -107,17 +107,16 @@ public void afterWorkItemTransition(ProcessWorkItemTransitionEvent event) { 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(); + UserTaskInstance userTaskInstance_1 = userTasks.instances().findById(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")); + userTaskList = userTasks.instances().findByIdentity(IdentityProviders.of("invalid")); assertThat(userTaskList).hasSize(0); - userTaskList = userTask_1.instances().findByIdentity(IdentityProviders.of("john")); + userTaskList = userTasks.instances().findByIdentity(IdentityProviders.of("john")); assertThat(userTaskList).hasSize(1); userTaskInstance_1 = userTaskList.get(0); @@ -131,8 +130,7 @@ public void afterWorkItemTransition(ProcessWorkItemTransitionEvent event) { 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(); + UserTaskInstance userTaskInstance_2 = userTasks.instances().findById(wi_2.getExternalReferenceId()).get(); assertThat(userTaskInstance_2).isNotNull(); userTaskList = userTasks.instances().findByIdentity(IdentityProviders.of("john")); @@ -146,12 +144,44 @@ public void afterWorkItemTransition(ProcessWorkItemTransitionEvent event) { assertThat(workItemTransitionEvents).hasSize(8); } - private String getUserTaskInstanceId(String externalReference) { - return externalReference.split(":")[1]; - } + @Test + public void testDoubleLinkUserTaskProcesses() throws Exception { + + Application app = generateCodeProcessesOnly("usertask/UserTasksProcess.bpmn2"); + assertThat(app).isNotNull(); + + // 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(); + Map parameters = new HashMap<>(); + m.fromMap(parameters); + + ProcessInstance processInstance = p.createInstance(m); + processInstance.start(); + + assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE); + + List workItems = processInstance.workItems(); + assertThat(workItems).hasSize(1); + assertThat(workItems.get(0).getName()).isEqualTo("FirstTask"); + WorkItem wi_1 = workItems.get(0); - private String getUserTaskId(String externalReference) { - return externalReference.split(":")[0]; + UserTasks userTasks = app.get(UserTasks.class); + + UserTaskInstance userTaskInstance_1 = userTasks.instances().findById(wi_1.getExternalReferenceId()).get(); + assertThat(userTaskInstance_1).isNotNull(); + + List userTaskList = userTasks.instances().findByIdentity(IdentityProviders.of("mary")); + assertThat(userTaskList).hasSize(1); + // now we check the external reference properly sets one to the other + Optional utLinked = userTasks.instances().findById(wi_1.getExternalReferenceId()); + assertThat(utLinked).isPresent().get().extracting(UserTaskInstance::getExternalReferenceId).isEqualTo(wi_1.getId()); + assertThat(wi_1.getExternalReferenceId()).isEqualTo(utLinked.get().getId()); + + processInstance.abort(); } @Test @@ -396,7 +426,7 @@ public void testBasicUserTaskProcessClaimAndCompleteWrongUser() throws Exception 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"))); + assertThatExceptionOfType(UserTaskInstanceNotAuthorizedException.class).isThrownBy(() -> utInvalid.transition(CLAIM, emptyMap(), IdentityProviders.of("invalid"))); assertThat(processInstance.status()).isEqualTo(ProcessInstance.STATE_ACTIVE); 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 86e6bd80531..528a9e081a0 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 @@ -62,6 +62,7 @@ import org.kie.kogito.codegen.process.config.ProcessConfigGenerator; import org.kie.kogito.codegen.process.events.ProcessCloudEventMeta; import org.kie.kogito.codegen.process.events.ProcessCloudEventMetaFactoryGenerator; +import org.kie.kogito.codegen.process.util.CodegenUtil; import org.kie.kogito.internal.SupportedExtensions; import org.kie.kogito.internal.process.runtime.KogitoWorkflowProcess; import org.kie.kogito.process.validation.ValidationException; @@ -76,7 +77,6 @@ import static java.lang.String.format; import static java.util.stream.Collectors.toList; -import static org.kie.kogito.codegen.process.ProcessResourceGenerator.TRANSACTION_ENABLED; import static org.kie.kogito.grafana.GrafanaConfigurationWriter.buildDashboardName; import static org.kie.kogito.grafana.GrafanaConfigurationWriter.generateOperationalDashboard; import static org.kie.kogito.internal.utils.ConversionUtils.sanitizeClassName; @@ -369,7 +369,7 @@ protected Collection internalGenerate() { .withWorkItems(processIdToWorkItemModel.get(workFlowProcess.getId())) .withSignals(metaData.getSignals()) .withTriggers(metaData.isStartable(), metaData.isDynamic(), metaData.getTriggers()) - .withTransaction(isTransactionEnabled()); + .withTransaction(CodegenUtil.isTransactionEnabled(this, context())); rgs.add(processResourceGenerator); } @@ -511,11 +511,6 @@ protected Collection internalGenerate() { return generatedFiles; } - protected boolean isTransactionEnabled() { - String processTransactionProperty = String.format("kogito.%s.%s", GENERATOR_NAME, TRANSACTION_ENABLED); - return "true".equalsIgnoreCase(context().getApplicationProperty(processTransactionProperty).orElse("true")); - } - private void storeFile(GeneratedFileType type, String path, String source) { if (generatedFiles.stream().anyMatch(f -> path.equals(f.relativePath()))) { LOGGER.warn("There's already a generated file named {} to be compiled. Ignoring.", path); 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 3e2f8765a40..207af9e6369 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 @@ -77,11 +77,6 @@ */ public class ProcessResourceGenerator { - /** - * Flag used to configure transaction enablement. Default to true - */ - public static final String TRANSACTION_ENABLED = "transactionEnabled"; - static final String INVALID_CONTEXT_TEMPLATE = "ProcessResourceGenerator can't be used for context without Rest %s"; private static final Logger LOG = LoggerFactory.getLogger(ProcessResourceGenerator.class); diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/util/CodegenUtil.java b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/util/CodegenUtil.java new file mode 100644 index 00000000000..ecf9737e170 --- /dev/null +++ b/kogito-codegen-modules/kogito-codegen-processes/src/main/java/org/kie/kogito/codegen/process/util/CodegenUtil.java @@ -0,0 +1,105 @@ +/* + * 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.codegen.process.util; + +import java.util.function.Function; + +import org.kie.kogito.codegen.api.Generator; +import org.kie.kogito.codegen.api.context.KogitoBuildContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class CodegenUtil { + + private static final Logger LOG = LoggerFactory.getLogger(CodegenUtil.class); + /** + * Flag used to configure transaction enabling. Default to true + */ + public static final String TRANSACTION_ENABLED = "transactionEnabled"; + + private CodegenUtil() { + // do nothing + } + + /** + * Creates the property for a certain generator. + * + * @param generator + * @param propertyName + * @return returns the property for certain generator + */ + public static String generatorProperty(Generator generator, String propertyName) { + return String.format("kogito.%s.%s", generator.name(), propertyName); + } + + /** + * Creates the property for global application + * + * @param propertyName + * @return + */ + public static String globalProperty(String propertyName) { + return String.format("kogito.%s", propertyName); + } + + /** + * This computes the boolean value of the transaction being enabled based on the logic specified + * the property. Default value it is true + * + * @see CodegenUtil#getProperty + */ + public static boolean isTransactionEnabled(Generator generator, KogitoBuildContext context) { + boolean propertyValue = getProperty(generator, context, TRANSACTION_ENABLED, Boolean::parseBoolean, true); + LOG.debug("trying to compute property {} for generator {} property with value {}", TRANSACTION_ENABLED, generator.name(), propertyValue); + return propertyValue; + } + + /** + * This method is a generic method to compute certain property of the given type. + * 1. we compute the global property applicable for all the application. + * 2. we compute the property only applicable for certain generator. + * + * @see CodegenUtil#getApplicationProperty + * @see CodegenUtil#globalProperty + * @see CodegenUtil#generatorProperty + */ + public static T getProperty(Generator generator, KogitoBuildContext context, String propertyName, Function converter, T defaultValue) { + + String generatorProperty = generatorProperty(generator, propertyName); + if (isApplicationPropertyDefined(context, generatorProperty)) { + return converter.apply(getApplicationProperty(context, generatorProperty)); + } + + String globalProperty = globalProperty(propertyName); + + if (isApplicationPropertyDefined(context, globalProperty)) { + return converter.apply(getApplicationProperty(context, globalProperty)); + } + + return defaultValue; + } + + private static boolean isApplicationPropertyDefined(KogitoBuildContext context, String property) { + return context.getApplicationProperty(property).isPresent(); + } + + private static String getApplicationProperty(KogitoBuildContext context, String property) { + return context.getApplicationProperty(property).orElseThrow(() -> new IllegalArgumentException("Property " + property + " defined but does not contain proper value")); + } +} 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 bd10069d470..9c2fa55b1ac 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 @@ -51,6 +51,7 @@ import org.kie.kogito.codegen.core.AbstractGenerator; import org.kie.kogito.codegen.process.ProcessCodegenException; import org.kie.kogito.codegen.process.ProcessParsingException; +import org.kie.kogito.codegen.process.util.CodegenUtil; import org.kie.kogito.internal.SupportedExtensions; import org.kie.kogito.internal.process.runtime.KogitoWorkflowProcess; import org.kie.kogito.process.validation.ValidationException; @@ -62,9 +63,9 @@ import com.github.javaparser.ast.NodeList; import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.body.ConstructorDeclaration; +import com.github.javaparser.ast.body.MethodDeclaration; import com.github.javaparser.ast.expr.CastExpr; import com.github.javaparser.ast.expr.Expression; -import com.github.javaparser.ast.expr.IntegerLiteralExpr; import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.expr.NullLiteralExpr; @@ -97,9 +98,9 @@ public class UserTaskCodegen extends AbstractGenerator { BPMN_SEMANTIC_MODULES.addSemanticModule(new BPMNDISemanticModule()); } - public static final String SECTION_CLASS_NAME = "usertask"; + public static final String SECTION_CLASS_NAME = "usertasks"; - TemplatedGenerator templateGenerator; + private TemplatedGenerator templateGenerator; private List descriptors; private TemplatedGenerator producerTemplateGenerator; private TemplatedGenerator restTemplateGenerator; @@ -158,25 +159,28 @@ protected Collection internalGenerate() { return generatedFiles; } - private GeneratedFile generateRestEndpiont() { + public GeneratedFile generateRestEndpiont() { String packageName = context().getPackageName(); - CompilationUnit compilationUnit = producerTemplateGenerator.compilationUnitOrThrow("Not rest endpoints template found for user tasks"); + CompilationUnit compilationUnit = restTemplateGenerator.compilationUnitOrThrow("Not rest endpoints template found for user tasks"); compilationUnit.setPackageDeclaration(packageName); + if (CodegenUtil.isTransactionEnabled(this, context())) { + compilationUnit.findAll(MethodDeclaration.class).stream().filter(MethodDeclaration::isPublic).forEach(context().getDependencyInjectionAnnotator()::withTransactional); + } 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()); + return new GeneratedFile(GeneratedFileType.REST, Path.of(urlBase).resolve(className + ".java"), compilationUnit.toString()); } - private GeneratedFile generateProducer() { + public GeneratedFile generateProducer() { String packageName = context().getPackageName(); - CompilationUnit compilationUnit = restTemplateGenerator.compilationUnitOrThrow("No producer template found for user tasks"); + CompilationUnit compilationUnit = producerTemplateGenerator.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() { + public List generateUserTask() { List generatedFiles = new ArrayList<>(); for (Work info : descriptors) { CompilationUnit unit = templateGenerator.compilationUnit().get(); @@ -212,7 +216,7 @@ private List generateUserTask() { block.addStatement(new MethodCallExpr(new ThisExpr(), "setExcludedUsers", NodeList.nodeList(toStringExpression(info.getParameter(EXCLUDED_OWNER_ID))))); block.addStatement(new MethodCallExpr(new ThisExpr(), "setTaskDescription", NodeList.nodeList(toStringExpression(info.getParameter(DESCRIPTION))))); - block.addStatement(new MethodCallExpr(new ThisExpr(), "setTaskPriority", NodeList.nodeList(toIntegerExpression(info.getParameter(PRIORITY))))); + block.addStatement(new MethodCallExpr(new ThisExpr(), "setTaskPriority", NodeList.nodeList(toStringExpression(info.getParameter(PRIORITY))))); block.addStatement(new MethodCallExpr(new ThisExpr(), "setReferenceName", NodeList.nodeList(toStringExpression(info.getParameter(NODE_NAME))))); block.addStatement(new MethodCallExpr(new ThisExpr(), "setSkippable", NodeList.nodeList(toStringExpression(info.getParameter("Skippable"))))); @@ -226,14 +230,6 @@ private List generateUserTask() { return generatedFiles; } - private Expression toIntegerExpression(Object value) { - if (value == null) { - return new CastExpr(StaticJavaParser.parseType(Integer.class.getName()), new NullLiteralExpr()); - } - - return new IntegerLiteralExpr(value.toString()); - } - private Expression toStringExpression(Object value) { if (value == null) { return new CastExpr(StaticJavaParser.parseType(String.class.getName()), new NullLiteralExpr()); 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 index 231dfcc737e..8a7039263d0 100644 --- 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 @@ -45,6 +45,7 @@ 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.UserTaskInstanceNotFoundException; import org.kie.kogito.usertask.UserTaskService; import org.kie.kogito.usertask.view.UserTaskView; import org.kie.kogito.usertask.view.UserTaskTransitionView; @@ -69,7 +70,7 @@ public List list(@QueryParam("user") String user, @QueryParam("gro @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); + return userTaskService.getUserTaskInstance(taskId, IdentityProviders.of(user, groups)).orElseThrow(UserTaskInstanceNotFoundException::new); } @POST @@ -78,10 +79,10 @@ public UserTaskView find(@PathParam("taskId") String taskId, @QueryParam("user") @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); + @QueryParam("group") List groups, + TransitionInfo transitionInfo) { + return userTaskService.transition(taskId, transitionInfo.getTransitionId(), transitionInfo.getData(), IdentityProviders.of(user, groups)).orElseThrow(UserTaskInstanceNotFoundException::new); } @GET @@ -102,7 +103,7 @@ public UserTaskView setOutput( @QueryParam("user") String user, @QueryParam("group") List groups, Map data) { - return userTaskService.setOutputs(taskId, data, IdentityProviders.of(user, groups)).orElseThrow(NotFoundException::new); + return userTaskService.setOutputs(taskId, data, IdentityProviders.of(user, groups)).orElseThrow(UserTaskInstanceNotFoundException::new); } @PUT @@ -113,7 +114,7 @@ public UserTaskView setOutput(@PathParam("id") String id, @QueryParam("user") String user, @QueryParam("group") List groups, Map data) { - return userTaskService.setInputs(taskId, data, IdentityProviders.of(user, groups)).orElseThrow(NotFoundException::new); + return userTaskService.setInputs(taskId, data, IdentityProviders.of(user, groups)).orElseThrow(UserTaskInstanceNotFoundException::new); } @GET @@ -137,7 +138,7 @@ public Comment addComment( CommentInfo commentInfo) { Comment comment = new Comment(null, user); comment.setContent(commentInfo.getComment()); - return userTaskService.addComment(taskId, comment, IdentityProviders.of(user, groups)).orElseThrow(NotFoundException::new); + return userTaskService.addComment(taskId, comment, IdentityProviders.of(user, groups)).orElseThrow(UserTaskInstanceNotFoundException::new); } @GET @@ -149,7 +150,7 @@ public Comment getComment( @QueryParam("user") String user, @QueryParam("group") List groups) { return userTaskService.getComment(taskId, commentId, IdentityProviders.of(user, groups)) - .orElseThrow(() -> new NotFoundException("Comment " + commentId + " not found")); + .orElseThrow(() -> new UserTaskInstanceNotFoundException("Comment " + commentId + " not found")); } @PUT @@ -165,7 +166,7 @@ public Comment updateComment( Comment comment = new Comment(commentId, user); comment.setContent(commentInfo.getComment()); return userTaskService.updateComment(taskId, comment, IdentityProviders.of(user, groups)) - .orElseThrow(NotFoundException::new); + .orElseThrow(UserTaskInstanceNotFoundException::new); } @DELETE @@ -176,7 +177,7 @@ public Comment deleteComment( @QueryParam("user") String user, @QueryParam("group") List groups) { return userTaskService.removeComment(taskId, commentId, IdentityProviders.of(user, groups)) - .orElseThrow(NotFoundException::new); + .orElseThrow(UserTaskInstanceNotFoundException::new); } @GET @@ -202,7 +203,7 @@ public Attachment addAttachment( attachment.setName(attachmentInfo.getName()); attachment.setContent(attachmentInfo.getUri()); return userTaskService.addAttachment(taskId, attachment, IdentityProviders.of(user, groups)) - .orElseThrow(NotFoundException::new); + .orElseThrow(UserTaskInstanceNotFoundException::new); } @PUT @@ -219,7 +220,7 @@ public Attachment updateAttachment( attachment.setName(attachmentInfo.getName()); attachment.setContent(attachmentInfo.getUri()); return userTaskService.updateAttachment(taskId, attachment, IdentityProviders.of(user, groups)) - .orElseThrow(NotFoundException::new); + .orElseThrow(UserTaskInstanceNotFoundException::new); } @DELETE @@ -230,7 +231,7 @@ public Attachment deleteAttachment( @QueryParam("user") String user, @QueryParam("group") List groups) { return userTaskService.removeAttachment(taskId, attachmentId, IdentityProviders.of(user, groups)) - .orElseThrow(NotFoundException::new); + .orElseThrow(UserTaskInstanceNotFoundException::new); } @GET @@ -242,7 +243,7 @@ public Attachment getAttachment( @QueryParam("user") String user, @QueryParam("group") List groups) { return userTaskService.getAttachment(taskId, attachmentId, IdentityProviders.of(user, groups)) - .orElseThrow(() -> new NotFoundException("Attachment " + attachmentId + " not found")); + .orElseThrow(() -> new UserTaskInstanceNotFoundException("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 index 87c757d3d79..4e0e7580276 100644 --- 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 @@ -71,11 +71,10 @@ public UserTaskView find(@PathVariable("taskId") String taskId, @RequestParam("u @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)); + @RequestBody TransitionInfo transitionInfo) { + return userTaskService.transition(taskId, transitionInfo.getTransitionId(), transitionInfo.getData(), IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); } @GetMapping(value = "/{taskId}/transition", produces = MediaType.APPLICATION_JSON_VALUE) @@ -145,7 +144,7 @@ public Comment updateComment( return userTaskService.updateComment(taskId, comment, IdentityProviders.of(user, groups)).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); } - @DeleteMapping("/{taskId}/comments/{commentId}") + @DeleteMapping(value = "/{taskId}/comments/{commentId}", consumes = MediaType.ALL_VALUE) public Comment deleteComment( @PathVariable("taskId") String taskId, @PathVariable("commentId") String commentId, @@ -188,7 +187,7 @@ public Attachment updateAttachment( .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); } - @DeleteMapping("/{taskId}/attachments/{attachmentId}") + @DeleteMapping(value = "/{taskId}/attachments/{attachmentId}", consumes = MediaType.ALL_VALUE) public Attachment deleteAttachment( @PathVariable("taskId") String taskId, @PathVariable("attachmentId") String attachmentId, diff --git a/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/process/ProcessResourceGeneratorTest.java b/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/process/ProcessResourceGeneratorTest.java index 52d1665166a..551d5b48b5d 100644 --- a/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/process/ProcessResourceGeneratorTest.java +++ b/kogito-codegen-modules/kogito-codegen-processes/src/test/java/org/kie/kogito/codegen/process/ProcessResourceGeneratorTest.java @@ -20,6 +20,7 @@ import java.io.File; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.function.Predicate; @@ -35,6 +36,8 @@ import org.kie.kogito.codegen.api.AddonsConfig; import org.kie.kogito.codegen.api.context.KogitoBuildContext; import org.kie.kogito.codegen.api.context.impl.JavaKogitoBuildContext; +import org.kie.kogito.codegen.process.util.CodegenUtil; +import org.kie.kogito.codegen.usertask.UserTaskCodegen; import org.kie.kogito.internal.process.runtime.KogitoWorkflowProcess; import com.github.javaparser.StaticJavaParser; @@ -209,6 +212,84 @@ void testManageTransactionalDisabled(KogitoBuildContext.Builder contextBuilder) testTransaction(restEndpoints, kogitoBuildContext, transactionEnabled); } + @ParameterizedTest + @MethodSource("org.kie.kogito.codegen.api.utils.KogitoContextTestUtils#restContextBuilders") + void testUserTaskManageTransactionalEnabledByDefault(KogitoBuildContext.Builder contextBuilder) { + KogitoBuildContext context = contextBuilder.build(); + UserTaskCodegen userTaskCodegen = new UserTaskCodegen(context, Collections.emptyList()); + CompilationUnit compilationUnit = StaticJavaParser.parse(new String(userTaskCodegen.generateRestEndpiont().contents())); + List restEndpoints = compilationUnit.findAll(MethodDeclaration.class).stream().filter(MethodDeclaration::isPublic).toList(); + assertThat(restEndpoints).isNotEmpty(); + for (MethodDeclaration method : restEndpoints) { + assertThat(findAnnotationExpr(method, context.getDependencyInjectionAnnotator().getTransactionalAnnotation())).isPresent(); + } + } + + @ParameterizedTest + @MethodSource("org.kie.kogito.codegen.api.utils.KogitoContextTestUtils#restContextBuilders") + void testUserTaskManageTransactionalDisabled(KogitoBuildContext.Builder contextBuilder) { + KogitoBuildContext context = contextBuilder.build(); + UserTaskCodegen userTaskCodegen = new UserTaskCodegen(context, Collections.emptyList()); + context.setApplicationProperty(CodegenUtil.generatorProperty(userTaskCodegen, CodegenUtil.TRANSACTION_ENABLED), "false"); + CompilationUnit compilationUnit = StaticJavaParser.parse(new String(userTaskCodegen.generateRestEndpiont().contents())); + List restEndpoints = compilationUnit.findAll(MethodDeclaration.class).stream().filter(MethodDeclaration::isPublic).toList(); + assertThat(restEndpoints).isNotEmpty(); + for (MethodDeclaration method : restEndpoints) { + assertThat(findAnnotationExpr(method, context.getDependencyInjectionAnnotator().getTransactionalAnnotation())).isEmpty(); + } + } + + @ParameterizedTest + @MethodSource("org.kie.kogito.codegen.api.utils.KogitoContextTestUtils#restContextBuilders") + void testUserTaskManageTransactionalGeneratorDisabled(KogitoBuildContext.Builder contextBuilder) { + KogitoBuildContext context = contextBuilder.build(); + UserTaskCodegen userTaskCodegen = new UserTaskCodegen(context, Collections.emptyList()); + context.setApplicationProperty(CodegenUtil.generatorProperty(userTaskCodegen, CodegenUtil.TRANSACTION_ENABLED), "false"); + CompilationUnit compilationUnit = StaticJavaParser.parse(new String(userTaskCodegen.generateRestEndpiont().contents())); + List restEndpoints = compilationUnit.findAll(MethodDeclaration.class).stream().filter(MethodDeclaration::isPublic).toList(); + assertThat(restEndpoints).isNotEmpty(); + for (MethodDeclaration method : restEndpoints) { + assertThat(findAnnotationExpr(method, context.getDependencyInjectionAnnotator().getTransactionalAnnotation())).isEmpty(); + } + } + + @ParameterizedTest + @MethodSource("org.kie.kogito.codegen.api.utils.KogitoContextTestUtils#restContextBuilders") + void testUserTaskManageTransactionalEnabled(KogitoBuildContext.Builder contextBuilder) { + KogitoBuildContext context = contextBuilder.build(); + UserTaskCodegen userTaskCodegen = new UserTaskCodegen(context, Collections.emptyList()); + context.setApplicationProperty(CodegenUtil.generatorProperty(userTaskCodegen, CodegenUtil.TRANSACTION_ENABLED), "true"); + CompilationUnit compilationUnit = StaticJavaParser.parse(new String(userTaskCodegen.generateRestEndpiont().contents())); + List restEndpoints = compilationUnit.findAll(MethodDeclaration.class).stream().filter(MethodDeclaration::isPublic).toList(); + assertThat(restEndpoints).isNotEmpty(); + for (MethodDeclaration method : restEndpoints) { + assertThat(findAnnotationExpr(method, context.getDependencyInjectionAnnotator().getTransactionalAnnotation())).isPresent(); + } + } + + @ParameterizedTest + @MethodSource("org.kie.kogito.codegen.api.utils.KogitoContextTestUtils#restContextBuilders") + void testUserTaskManageTransactionalGeneratorEnabled(KogitoBuildContext.Builder contextBuilder) { + KogitoBuildContext context = contextBuilder.build(); + UserTaskCodegen userTaskCodegen = new UserTaskCodegen(context, Collections.emptyList()); + context.setApplicationProperty(CodegenUtil.generatorProperty(userTaskCodegen, CodegenUtil.TRANSACTION_ENABLED), "true"); + CompilationUnit compilationUnit = StaticJavaParser.parse(new String(userTaskCodegen.generateRestEndpiont().contents())); + List restEndpoints = compilationUnit.findAll(MethodDeclaration.class).stream().filter(MethodDeclaration::isPublic).toList(); + assertThat(restEndpoints).isNotEmpty(); + for (MethodDeclaration method : restEndpoints) { + assertThat(findAnnotationExpr(method, context.getDependencyInjectionAnnotator().getTransactionalAnnotation())).isPresent(); + } + } + + Optional findAnnotationExpr(MethodDeclaration method, String fqn) { + for (AnnotationExpr expr : method.getAnnotations()) { + if (expr.getNameAsString().equals(fqn)) { + return Optional.of(expr); + } + } + return Optional.empty(); + } + void testTransaction(Collection restEndpoints, KogitoBuildContext kogitoBuildContext, boolean enabled) { diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-executor-core/src/main/java/org/kie/kogito/serverless/workflow/executor/StaticWorkflowApplication.java b/kogito-serverless-workflow/kogito-serverless-workflow-executor-core/src/main/java/org/kie/kogito/serverless/workflow/executor/StaticWorkflowApplication.java index 186d205ee86..a7015801087 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-executor-core/src/main/java/org/kie/kogito/serverless/workflow/executor/StaticWorkflowApplication.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-executor-core/src/main/java/org/kie/kogito/serverless/workflow/executor/StaticWorkflowApplication.java @@ -47,6 +47,8 @@ import org.kie.kogito.StaticConfig; import org.kie.kogito.codegen.api.context.impl.JavaKogitoBuildContext; import org.kie.kogito.config.StaticConfigBean; +import org.kie.kogito.event.EventManager; +import org.kie.kogito.event.EventPublisher; import org.kie.kogito.event.impl.EventFactoryUtils; import org.kie.kogito.internal.process.event.DefaultKogitoProcessEventListener; import org.kie.kogito.internal.process.event.KogitoProcessEventListener; @@ -65,6 +67,8 @@ import org.kie.kogito.serverless.workflow.utils.MultiSourceConfigResolver; import org.kie.kogito.services.uow.CollectingUnitOfWorkFactory; import org.kie.kogito.services.uow.DefaultUnitOfWorkManager; +import org.kie.kogito.services.uow.UnitOfWorkExecutor; +import org.kie.kogito.uow.UnitOfWorkManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -98,6 +102,7 @@ public class StaticWorkflowApplication extends StaticApplication implements Auto private Iterable processRegisters; private final Collection closeables = new ArrayList<>(); private final Map> queues; + private final UnitOfWorkManager manager; private ProcessInstancesFactory processInstancesFactory; private static class StaticCompletionEventListener extends DefaultKogitoProcessEventListener { @@ -127,7 +132,10 @@ public void afterProcessCompleted(ProcessCompletedEvent event) { public static class WorkflowApplicationBuilder { private Map properties; + private String serviceName = "EmbeddedKogito"; private Collection listeners = new ArrayList<>(); + private Optional manager = Optional.empty(); + private Collection publishers = new ArrayList<>(); private WorkflowApplicationBuilder() { } @@ -145,14 +153,36 @@ public WorkflowApplicationBuilder withEventListener(KogitoProcessEventListener l return this; } + public WorkflowApplicationBuilder withManager(UnitOfWorkManager manager) { + this.manager = Optional.ofNullable(manager); + return this; + } + + public WorkflowApplicationBuilder withService(String serviceName) { + this.serviceName = serviceName; + return this; + } + + public WorkflowApplicationBuilder withEventPublisher(EventPublisher publisher, EventPublisher... extraPublishers) { + publishers.add(publisher); + for (EventPublisher extraPublisher : extraPublishers) { + publishers.add(extraPublisher); + } + return this; + } + public StaticWorkflowApplication build() { if (properties == null) { this.properties = loadApplicationDotProperties(); } Map> queues = new ConcurrentHashMap<>(); listeners.add(new StaticCompletionEventListener(queues)); - StaticWorkflowApplication application = new StaticWorkflowApplication(properties, queues, listeners); + StaticWorkflowApplication application = + new StaticWorkflowApplication(properties, queues, listeners, manager.orElseGet(() -> new DefaultUnitOfWorkManager(new CollectingUnitOfWorkFactory()))); application.applicationRegisters.forEach(register -> register.register(application)); + EventManager eventManager = application.manager.eventManager(); + eventManager.setService(serviceName); + publishers.forEach(p -> eventManager.addPublisher(p)); return application; } } @@ -189,14 +219,15 @@ public static StaticWorkflowApplication create(Map properties) { return builder().withProperties(properties).build(); } - private StaticWorkflowApplication(Map properties, Map> queues, Collection listeners) { + private StaticWorkflowApplication(Map properties, Map> queues, Collection listeners, + UnitOfWorkManager manager) { super(new StaticConfig(new Addons(Collections.emptySet()), new StaticProcessConfig(new CachedWorkItemHandlerConfig(), - new DefaultProcessEventListenerConfig(listeners), - new DefaultUnitOfWorkManager(new CollectingUnitOfWorkFactory())), new StaticConfigBean())); + new DefaultProcessEventListenerConfig(listeners), manager), new StaticConfigBean())); if (!properties.isEmpty()) { ConfigResolverHolder.setConfigResolver(MultiSourceConfigResolver.withSystemProperties(properties)); } this.queues = queues; + this.manager = manager; applicationRegisters = ServiceLoader.load(StaticApplicationRegister.class); workflowRegisters = ServiceLoader.load(StaticWorkflowRegister.class); processRegisters = ServiceLoader.load(StaticProcessRegister.class); @@ -268,8 +299,10 @@ public JsonNodeModel execute(Process process, JsonNode data) { */ public JsonNodeModel execute(Process process, JsonNodeModel model) { ProcessInstance processInstance = process.createInstance(model); - processInstance.start(); - return processInstance.variables(); + return UnitOfWorkExecutor.executeInUnitOfWork(manager, () -> { + processInstance.start(); + return processInstance.variables(); + }); } /** diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-executor-tests/pom.xml b/kogito-serverless-workflow/kogito-serverless-workflow-executor-tests/pom.xml index 1aa1202830c..cb9fa645b24 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-executor-tests/pom.xml +++ b/kogito-serverless-workflow/kogito-serverless-workflow-executor-tests/pom.xml @@ -77,7 +77,7 @@ wiremock-jre8 test - + org.kie kie-addons-persistence-rocksdb test diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-executor-tests/src/test/java/org/kie/kogito/serverless/workflow/executor/EventPublisherCollector.java b/kogito-serverless-workflow/kogito-serverless-workflow-executor-tests/src/test/java/org/kie/kogito/serverless/workflow/executor/EventPublisherCollector.java new file mode 100644 index 00000000000..04cfd048f3e --- /dev/null +++ b/kogito-serverless-workflow/kogito-serverless-workflow-executor-tests/src/test/java/org/kie/kogito/serverless/workflow/executor/EventPublisherCollector.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.serverless.workflow.executor; + +import java.util.ArrayList; +import java.util.Collection; + +import org.kie.kogito.event.DataEvent; +import org.kie.kogito.event.EventPublisher; + +public class EventPublisherCollector implements EventPublisher { + + private Collection> events = new ArrayList<>(); + + @Override + public void publish(DataEvent event) { + events.add(event); + } + + @Override + public void publish(Collection> events) { + events.forEach(this::publish); + } + + public Collection> events() { + return events; + } + +} diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-executor-tests/src/test/java/org/kie/kogito/serverless/workflow/executor/PersistentApplicationTest.java b/kogito-serverless-workflow/kogito-serverless-workflow-executor-tests/src/test/java/org/kie/kogito/serverless/workflow/executor/PersistentApplicationTest.java index 251a50d532e..d677e4bd90c 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-executor-tests/src/test/java/org/kie/kogito/serverless/workflow/executor/PersistentApplicationTest.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-executor-tests/src/test/java/org/kie/kogito/serverless/workflow/executor/PersistentApplicationTest.java @@ -22,17 +22,27 @@ import java.nio.file.Path; import java.time.Duration; import java.time.OffsetDateTime; +import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.MockConsumer; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.kie.api.event.process.ProcessVariableChangedEvent; +import org.kie.kogito.event.process.ProcessInstanceVariableDataEvent; +import org.kie.kogito.event.process.ProcessInstanceVariableEventBody; +import org.kie.kogito.internal.process.event.DefaultKogitoProcessEventListener; import org.kie.kogito.persistence.rocksdb.RocksDBProcessInstancesFactory; +import org.kie.kogito.serverless.workflow.SWFConstants; import org.rocksdb.Options; import org.rocksdb.RocksDBException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.node.TextNode; @@ -51,21 +61,37 @@ import static org.kie.kogito.serverless.workflow.fluent.WorkflowBuilder.workflow; public class PersistentApplicationTest { + + private final static Logger logger = LoggerFactory.getLogger(PersistentApplicationTest.class); + @Test void testCallbackSubscriberWithPersistence(@TempDir Path tempDir) throws InterruptedException, TimeoutException, RocksDBException { final String eventType = "testSubscribe"; final String additionalData = "This has been injected by the event"; - Workflow workflow = workflow("testCallback").start(callback(call(expr("concat", "{slogan:.slogan+\"er Beti\"}")), eventDef(eventType))).end().build(); + + final EventPublisherCollector eventCollector = new EventPublisherCollector(); + Workflow workflow = workflow("testCallback").start(callback(call(expr("concat", "{slogan:.slogan+\"Viva er Beti manque pierda\"}")), eventDef(eventType))).end().build(); try (StaticWorkflowApplication application = - StaticWorkflowApplication.create().processInstancesFactory(new RocksDBProcessInstancesFactory(new Options().setCreateIfMissing(true), tempDir.toString()))) { - String id = application.execute(workflow, jsonObject().put("slogan", "Viva ")).getId(); + StaticWorkflowApplication.builder().withEventListener(new DefaultKogitoProcessEventListener() { + @Override + public void afterVariableChanged(ProcessVariableChangedEvent event) { + logger.info(event.toString()); + + } + }).withEventPublisher(eventCollector).build().processInstancesFactory(new RocksDBProcessInstancesFactory(new Options().setCreateIfMissing(true), tempDir.toString()))) { + String id = application.execute(workflow, Map.of()).getId(); assertThat(application.variables(id).orElseThrow().getWorkflowdata()).doesNotContain(new TextNode(additionalData)); publish(eventType, buildCloudEvent(eventType, id) .withData(JsonCloudEventData.wrap(jsonObject().put("additionalData", additionalData))) .build()); assertThat(application.waitForFinish(id, Duration.ofSeconds(2000)).orElseThrow().getWorkflowdata()) - .isEqualTo(jsonObject().put("additionalData", additionalData).put("slogan", "Viva er Beti")); + .isEqualTo(jsonObject().put("additionalData", additionalData).put("slogan", "Viva er Beti manque pierda")); await().atMost(Duration.ofSeconds(1)).pollInterval(Duration.ofMillis(50)).until(() -> application.variables(id).isEmpty()); + List dataChangeEvents = eventCollector.events().stream().filter(ProcessInstanceVariableDataEvent.class::isInstance) + .map(ProcessInstanceVariableDataEvent.class::cast).map(ProcessInstanceVariableDataEvent::getData).collect(Collectors.toList()); + assertThat(dataChangeEvents).hasSize(2); + assertThat(dataChangeEvents.get(0).getVariableName()).isEqualTo(SWFConstants.DEFAULT_WORKFLOW_VAR); + assertThat(dataChangeEvents.get(1).getVariableName()).isEqualTo(SWFConstants.DEFAULT_WORKFLOW_VAR + ".additionalData"); } } diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-executor-tests/src/test/resources/logback.xml b/kogito-serverless-workflow/kogito-serverless-workflow-executor-tests/src/test/resources/logback.xml new file mode 100644 index 00000000000..81ff4fee166 --- /dev/null +++ b/kogito-serverless-workflow/kogito-serverless-workflow-executor-tests/src/test/resources/logback.xml @@ -0,0 +1,11 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + \ No newline at end of file diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/WorkflowWorkItemHandler.java b/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/WorkflowWorkItemHandler.java index 6c08732ec69..289f319895c 100644 --- a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/WorkflowWorkItemHandler.java +++ b/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/main/java/org/kie/kogito/serverless/workflow/WorkflowWorkItemHandler.java @@ -48,11 +48,38 @@ public Optional activateWorkItemHandler(KogitoWorkItemManage protected abstract Object internalExecute(KogitoWorkItem workItem, Map parameters); + protected static C safeCast(Object obj, Class clazz) { + return obj == null || clazz.isAssignableFrom(obj.getClass()) ? clazz.cast(obj) : tryConvert(obj, clazz); + } + + private static C tryConvert(Object obj, Class clazz) { + if (Number.class.isAssignableFrom(clazz) && Number.class.isAssignableFrom(obj.getClass())) { + Number number = (Number) obj; + if (Integer.class.isAssignableFrom(clazz)) { + return clazz.cast(number.intValue()); + } else if (Long.class.isAssignableFrom(clazz)) { + return clazz.cast(number.longValue()); + } else if (Double.class.isAssignableFrom(clazz)) { + return clazz.cast(number.doubleValue()); + } else if (Float.class.isAssignableFrom(clazz)) { + return clazz.cast(number.floatValue()); + } else if (Short.class.isAssignableFrom(clazz)) { + return clazz.cast(number.shortValue()); + } else if (Byte.class.isAssignableFrom(clazz)) { + return clazz.cast(number.byteValue()); + } else { + throw new UnsupportedOperationException("Type " + clazz + " is not supported"); + } + } else { + throw new ClassCastException("OpenAPI expect type " + clazz + " but argument " + obj + " is of type " + obj.getClass()); + } + } + protected static V buildBody(Map params, Class clazz) { for (Object obj : params.values()) { if (obj != null && clazz.isAssignableFrom(obj.getClass())) { logger.trace("Invoking workitemhandler with value {}", obj); - return clazz.cast(obj); + return safeCast(obj, clazz); } } V value = JsonObjectUtils.convertValue(params, clazz); diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/test/java/org/kie/kogito/serverless/workflow/WorkflowWorkItemHandlerTest.java b/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/test/java/org/kie/kogito/serverless/workflow/WorkflowWorkItemHandlerTest.java new file mode 100644 index 00000000000..0d183c3571a --- /dev/null +++ b/kogito-serverless-workflow/kogito-serverless-workflow-runtime/src/test/java/org/kie/kogito/serverless/workflow/WorkflowWorkItemHandlerTest.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.kie.kogito.serverless.workflow; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.kie.kogito.serverless.workflow.WorkflowWorkItemHandler.safeCast; + +public class WorkflowWorkItemHandlerTest { + + @Test + void testSafeCast() { + assertThat(safeCast(4, Long.class)).isInstanceOf(Long.class).isEqualTo(4); + assertThat(safeCast(4L, Integer.class)).isInstanceOf(Integer.class).isEqualTo(4); + assertThat(safeCast(4, Float.class)).isInstanceOf(Float.class).isEqualTo(4); + assertThat(safeCast(4, Double.class)).isInstanceOf(Double.class).isEqualTo(4); + assertThat(safeCast(1.5f, Long.class)).isInstanceOf(Long.class).isEqualTo(1); + assertThat(safeCast(1.5f, Integer.class)).isInstanceOf(Integer.class).isEqualTo(1); + assertThat(safeCast(1.5, Float.class)).isInstanceOf(Float.class).isEqualTo(1.5f); + assertThat(safeCast(1.5f, Double.class)).isInstanceOf(Double.class).isEqualTo(1.5); + } +} 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 index ee1f284b3a8..66e007d0394 100644 --- 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 @@ -32,12 +32,12 @@ import org.kie.kogito.event.usertask.MultipleUserTaskInstanceDataEvent; import org.kie.kogito.event.usertask.UserTaskInstanceDataEvent; -import io.quarkus.arc.properties.IfBuildProperty; +import io.quarkus.arc.lookup.LookupIfProperty; import jakarta.inject.Singleton; @Singleton -@IfBuildProperty(name = "kogito.events.grouping", stringValue = "true") +@LookupIfProperty(name = "kogito.events.grouping", stringValue = "true") public class GroupingMessagingEventPublisher extends AbstractMessagingEventPublisher { @Override 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 c6aa4424f0c..bf9de3b9c2f 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 @@ -22,12 +22,12 @@ import org.kie.kogito.event.DataEvent; -import io.quarkus.arc.properties.UnlessBuildProperty; +import io.quarkus.arc.lookup.LookupUnlessProperty; import jakarta.inject.Singleton; @Singleton -@UnlessBuildProperty(name = "kogito.events.grouping", stringValue = "true", enableIfMissing = true) +@LookupUnlessProperty(name = "kogito.events.grouping", stringValue = "true", lookupIfMissing = true) public class ReactiveMessagingEventPublisher extends AbstractMessagingEventPublisher { @Override diff --git a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskInstanceNotAuthorizedExceptionMapper.java b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskInstanceNotAuthorizedExceptionMapper.java new file mode 100644 index 00000000000..98710328810 --- /dev/null +++ b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskInstanceNotAuthorizedExceptionMapper.java @@ -0,0 +1,33 @@ +/* + * 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.resource.exceptions; + +import org.kie.kogito.usertask.UserTaskInstanceNotFoundException; + +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.Provider; + +@Provider +public class UserTaskInstanceNotAuthorizedExceptionMapper extends BaseExceptionMapper { + + @Override + public Response toResponse(UserTaskInstanceNotFoundException e) { + return exceptionsHandler.mapException(e); + } +} diff --git a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskInstanceNotFoundExceptionMapper.java b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskInstanceNotFoundExceptionMapper.java new file mode 100644 index 00000000000..d74ddf1fc5c --- /dev/null +++ b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskInstanceNotFoundExceptionMapper.java @@ -0,0 +1,33 @@ +/* + * 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.resource.exceptions; + +import org.kie.kogito.usertask.UserTaskInstanceNotAuthorizedException; + +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.Provider; + +@Provider +public class UserTaskInstanceNotFoundExceptionMapper extends BaseExceptionMapper { + + @Override + public Response toResponse(UserTaskInstanceNotAuthorizedException e) { + return exceptionsHandler.mapException(e); + } +} diff --git a/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskTransitionExceptionMapper.java b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskTransitionExceptionMapper.java new file mode 100644 index 00000000000..233d3cd679a --- /dev/null +++ b/quarkus/addons/rest-exception-handler/src/main/java/org/kie/kogito/resource/exceptions/UserTaskTransitionExceptionMapper.java @@ -0,0 +1,33 @@ +/* + * 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.resource.exceptions; + +import org.kie.kogito.usertask.UserTaskInstanceNotFoundException; + +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.Provider; + +@Provider +public class UserTaskTransitionExceptionMapper extends BaseExceptionMapper { + + @Override + public Response toResponse(UserTaskInstanceNotFoundException e) { + return exceptionsHandler.mapException(e); + } +} diff --git a/quarkus/addons/task-management/runtime/src/main/java/org/kie/kogito/task/management/TaskManagementResource.java b/quarkus/addons/task-management/runtime/src/main/java/org/kie/kogito/task/management/TaskManagementResource.java index 538d1e3b435..d87f5ca2fed 100644 --- a/quarkus/addons/task-management/runtime/src/main/java/org/kie/kogito/task/management/TaskManagementResource.java +++ b/quarkus/addons/task-management/runtime/src/main/java/org/kie/kogito/task/management/TaskManagementResource.java @@ -20,12 +20,12 @@ import java.util.List; -import org.kie.kogito.auth.SecurityPolicy; import org.kie.kogito.process.ProcessConfig; -import org.kie.kogito.process.Processes; import org.kie.kogito.task.management.service.TaskInfo; import org.kie.kogito.task.management.service.TaskManagementOperations; import org.kie.kogito.task.management.service.TaskManagementService; +import org.kie.kogito.usertask.UserTaskConfig; +import org.kie.kogito.usertask.UserTasks; import jakarta.annotation.PostConstruct; import jakarta.inject.Inject; @@ -40,58 +40,58 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; -@Path("/management/processes") +@Path("/management/usertasks") public class TaskManagementResource { private TaskManagementOperations taskService; @Inject - private Processes processes; + private UserTasks userTasks; + + @Inject + private UserTaskConfig userTaskConfig; @Inject private ProcessConfig processConfig; @PostConstruct private void init() { - taskService = new TaskManagementService(processes, processConfig); + taskService = new TaskManagementService(userTasks, userTaskConfig, processConfig); } @PUT @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) - @Path("{processId}/instances/{processInstanceId}/tasks/{taskId}") - public Response updateTask(@PathParam("processId") String processId, - @PathParam("processInstanceId") String processInstanceId, + @Path("{taskId}") + public Response updateTask( @PathParam("taskId") String taskId, @QueryParam("user") final String user, @QueryParam("group") final List groups, TaskInfo taskInfo) { - taskService.updateTask(processId, processInstanceId, taskId, taskInfo, true, SecurityPolicy.of(user, groups)); + taskService.updateTask(taskId, taskInfo, true); return Response.ok().build(); } @PATCH @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) - @Path("{processId}/instances/{processInstanceId}/tasks/{taskId}") - public TaskInfo partialUpdateTask(@PathParam("processId") String processId, - @PathParam("processInstanceId") String processInstanceId, + @Path("{taskId}") + public TaskInfo partialUpdateTask( @PathParam("taskId") String taskId, @QueryParam("user") final String user, @QueryParam("group") final List groups, TaskInfo taskInfo) { - return taskService.updateTask(processId, processInstanceId, taskId, taskInfo, false, SecurityPolicy.of(user, groups)); + return taskService.updateTask(taskId, taskInfo, false); } @GET @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) - @Path("{processId}/instances/{processInstanceId}/tasks/{taskId}") - public TaskInfo getTask(@PathParam("processId") String processId, - @PathParam("processInstanceId") String processInstanceId, + @Path("{taskId}") + public TaskInfo getTask( @PathParam("taskId") String taskId, @QueryParam("user") final String user, @QueryParam("group") final List groups) { - return taskService.getTask(processId, processInstanceId, taskId, SecurityPolicy.of(user, groups)); + return taskService.getTask(taskId); } } diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/quarkus/serverless/workflow/openapi/WorkflowOpenApiHandlerGenerator.java b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/quarkus/serverless/workflow/openapi/WorkflowOpenApiHandlerGenerator.java index 214abfcd260..68f762768a6 100644 --- a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/quarkus/serverless/workflow/openapi/WorkflowOpenApiHandlerGenerator.java +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-deployment/src/main/java/org/kie/kogito/quarkus/serverless/workflow/openapi/WorkflowOpenApiHandlerGenerator.java @@ -39,7 +39,6 @@ import com.github.javaparser.ast.Modifier.Keyword; import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.body.MethodDeclaration; -import com.github.javaparser.ast.expr.CastExpr; import com.github.javaparser.ast.expr.ClassExpr; import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.expr.NameExpr; @@ -108,7 +107,8 @@ private WorkflowHandlerGeneratedFile generateHandler(KogitoBuildContext context, // Using deprecated args method because it is the only way to make it work across Quarkus main and 2.7 Type param = m.args()[i]; if (annotation != null) { - methodCallExpr.addArgument(new CastExpr(fromClass(param), new MethodCallExpr(parameters, "remove").addArgument(new StringLiteralExpr(annotation.value().asString())))); + methodCallExpr.addArgument(new MethodCallExpr("safeCast").addArgument(new MethodCallExpr(parameters, "remove").addArgument(new StringLiteralExpr(annotation.value().asString()))) + .addArgument(new ClassExpr(fromClass(param, false)))); } else { methodCallExpr.addArgument(new MethodCallExpr("buildBody").addArgument(parameters).addArgument(new ClassExpr(fromClass(param, false)))); } diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/application.properties b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/application.properties index 4148d965723..24a07fd6c3d 100644 --- a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/application.properties +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/application.properties @@ -39,8 +39,10 @@ quarkus.kubernetes-client.devservices.enabled=false # OpenApi client properties, see OperationsMockService, which is mocking these two services quarkus.rest-client.multiplication.cluster1.url=${multiplication-service-mock.url} quarkus.rest-client.subtraction.url=${subtraction-service-mock.url} +quarkus.rest-client.petstore_openapi_yaml.url=${petstore-service-mock.url} quarkus.rest-client.array_yaml.url=${array-service-mock.url} + # OpenApi client properties to access the general purpose external-service, which is mocked by the ExternalServiceMock quarkus.rest-client.external_service_yaml.url=${external-service-mock.url} diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/long-call.sw.yaml b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/long-call.sw.yaml new file mode 100644 index 00000000000..729f4ff8449 --- /dev/null +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/long-call.sw.yaml @@ -0,0 +1,21 @@ +id: long-call +version: "1.0" +specVersion: 0.8.0 +name: Long call +description: Description +start: Long call +functions: +- name: 'getPetById' + operation: 'specs/petstore.openapi.yaml#getPetById' + type: rest +states: + - name: Long call + type: operation + actions: + - name: 'Get Pet By Id' + functionRef: + invoke: sync + refName: getPetById + arguments: + petId: 4 + end: true diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/specs/petstore.openapi.yaml b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/specs/petstore.openapi.yaml new file mode 100644 index 00000000000..6112ca966a2 --- /dev/null +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/main/resources/specs/petstore.openapi.yaml @@ -0,0 +1,760 @@ +openapi: 3.0.2 +info: + title: Swagger Petstore - OpenAPI 3.0 + description: |- + This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about + Swagger at [http://swagger.io](http://swagger.io). In the third iteration of the pet store, we've switched to the design first approach! + You can now help us improve the API whether it's by making changes to the definition itself or to the code. + That way, with time, we can improve the API in general, and expose some of the new features in OAS3. + + Some useful links: + - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore) + - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml) + termsOfService: http://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.19 +externalDocs: + description: Find out more about Swagger + url: http://swagger.io +servers: + - url: /api/v3 +tags: + - name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: http://swagger.io + - name: store + description: Access to Petstore orders + externalDocs: + description: Find out more about our store + url: http://swagger.io + - name: user + description: Operations about user +paths: + /pet: + put: + tags: + - pet + summary: Update an existing pet + description: Update an existing pet by Id + operationId: updatePet + requestBody: + description: Update an existent pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + "200": + description: Successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid ID supplied + "404": + description: Pet not found + "405": + description: Validation exception + + post: + tags: + - pet + summary: Add a new pet to the store + description: Add a new pet to the store + operationId: addPet + requestBody: + description: Create a new pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + "200": + description: Successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + "405": + description: Invalid input + + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: false + explode: true + schema: + type: string + default: available + enum: + - available + - pending + - sold + responses: + "200": + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid status value + + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: false + explode: true + schema: + type: array + items: + type: string + responses: + "200": + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid tag value + + /pet/{petId}: + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid ID supplied + "404": + description: Pet not found + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: "" + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + - name: name + in: query + description: Name of pet that needs to be updated + schema: + type: string + - name: status + in: query + description: Status of pet that needs to be updated + schema: + type: string + responses: + "405": + description: Invalid input + + delete: + tags: + - pet + summary: Deletes a pet + description: "" + operationId: deletePet + parameters: + - name: api_key + in: header + description: "" + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + "400": + description: Invalid pet value + + /pet/{petId}/uploadImage: + post: + tags: + - pet + summary: uploads an image + description: "" + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + - name: additionalMetadata + in: query + description: Additional Metadata + required: false + schema: + type: string + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + + /store/order: + post: + tags: + - store + summary: Place an order for a pet + description: Place a new order in the store + operationId: placeOrder + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Order' + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + "405": + description: Invalid input + /store/order/{orderId}: + get: + tags: + - store + summary: Find purchase order by ID + description: For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions. + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of order that needs to be fetched + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + "400": + description: Invalid ID supplied + "404": + description: Order not found + delete: + tags: + - store + summary: Delete purchase order by ID + description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: integer + format: int64 + responses: + "400": + description: Invalid ID supplied + "404": + description: Order not found + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + requestBody: + description: Created user object + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + default: + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + description: Creates list of users with given input array + operationId: createUsersWithListInput + requestBody: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + responses: + "200": + description: Successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + default: + description: successful operation + /user/login: + get: + tags: + - user + summary: Logs user into the system + description: "" + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: false + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: false + schema: + type: string + responses: + "200": + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + "400": + description: Invalid username/password supplied + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session + description: "" + operationId: logoutUser + parameters: [] + responses: + default: + description: successful operation + /user/{username}: + get: + tags: + - user + summary: Get user by user name + description: "" + operationId: getUserByName + parameters: + - name: username + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + schema: + type: string + responses: + "200": + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + "400": + description: Invalid username supplied + "404": + description: User not found + put: + tags: + - user + summary: Update user + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that needs to be updated + required: true + schema: + type: string + requestBody: + description: Update an existent user in the store + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + default: + description: successful operation + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + "400": + description: Invalid username supplied + "404": + description: User not found +components: + schemas: + Order: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + petId: + type: integer + format: int64 + example: 198772 + quantity: + type: integer + format: int32 + example: 7 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + example: approved + enum: + - placed + - approved + - delivered + complete: + type: boolean + xml: + name: order + Customer: + type: object + properties: + id: + type: integer + format: int64 + example: 100000 + username: + type: string + example: fehguy + address: + type: array + xml: + name: addresses + wrapped: true + items: + $ref: '#/components/schemas/Address' + xml: + name: customer + Address: + type: object + properties: + street: + type: string + example: 437 Lytton + city: + type: string + example: Palo Alto + state: + type: string + example: CA + zip: + type: string + example: "94301" + xml: + name: address + Category: + type: object + properties: + id: + type: integer + format: int64 + example: 1 + name: + type: string + example: Dogs + xml: + name: category + User: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: "12345" + phone: + type: string + example: "12345" + userStatus: + type: integer + description: User Status + format: int32 + example: 1 + xml: + name: user + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: tag + Pet: + required: + - name + - photoUrls + type: object + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + category: + $ref: '#/components/schemas/Category' + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + xml: + name: '##default' + requestBodies: + Pet: + description: Pet object that needs to be added to the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + UserArray: + description: List of user object + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/test/java/org/kie/kogito/quarkus/workflows/AbstractCallbackStateIT.java b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/test/java/org/kie/kogito/quarkus/workflows/AbstractCallbackStateIT.java index 9b753f9566f..d8e204a2a54 100644 --- a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/test/java/org/kie/kogito/quarkus/workflows/AbstractCallbackStateIT.java +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/test/java/org/kie/kogito/quarkus/workflows/AbstractCallbackStateIT.java @@ -118,11 +118,8 @@ String executeCallbackStateWithErrorPath(String callbackProcessPostUrl, String c assertThat(lastExecutedState).isEqualTo("FinalizeWithError"); JsonPath variableLastExecutedStateEventContent = - waitForKogitoProcessInstanceEvent(kafkaClient, ProcessInstanceVariableDataEvent.class, e -> "workflowdata".equals(e.get("data.variableName")), true); - Map lastExecutedStateDataMap = variableLastExecutedStateEventContent.getMap("data.variableValue"); - - assertThat(lastExecutedStateDataMap).containsEntry("lastExecutedState", "FinalizeWithError"); - assertThat(lastExecutedStateDataMap).containsEntry("query", GENERATE_ERROR_QUERY); + waitForKogitoProcessInstanceEvent(kafkaClient, ProcessInstanceVariableDataEvent.class, e -> "workflowdata.lastExecutedState".equals(e.get("data.variableName")), true); + assertThat(variableLastExecutedStateEventContent.getString("data.variableValue")).isEqualTo("FinalizeWithError"); // the process instance should not be there since an end state was reached. assertProcessInstanceNotExists(callbackProcessGetByIdUrl, processInstanceId); diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/test/java/org/kie/kogito/quarkus/workflows/OpenAPIArrayFlowIT.java b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/test/java/org/kie/kogito/quarkus/workflows/OpenAPIArrayFlowIT.java index c4923c191bb..058e1acc8eb 100644 --- a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/test/java/org/kie/kogito/quarkus/workflows/OpenAPIArrayFlowIT.java +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/test/java/org/kie/kogito/quarkus/workflows/OpenAPIArrayFlowIT.java @@ -54,4 +54,16 @@ void testArray() { .body("id", notNullValue()) .body("workflowdata.response", is(Arrays.asList(1, 2, 3, 4))); } + + @Test + void testInt() { + given() + .contentType(ContentType.JSON) + .when() + .body(Collections.emptyMap()) + .post("/long-call") + .then() + .statusCode(201) + .body("id", notNullValue()); + } } diff --git a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/test/java/org/kie/kogito/quarkus/workflows/OpenAPIArrayMockService.java b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/test/java/org/kie/kogito/quarkus/workflows/OpenAPIArrayMockService.java index c67a911d8e3..136df12198b 100644 --- a/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/test/java/org/kie/kogito/quarkus/workflows/OpenAPIArrayMockService.java +++ b/quarkus/extensions/kogito-quarkus-serverless-workflow-extension/kogito-quarkus-serverless-workflow-integration-test/src/test/java/org/kie/kogito/quarkus/workflows/OpenAPIArrayMockService.java @@ -19,7 +19,6 @@ package org.kie.kogito.quarkus.workflows; import java.util.Map; -import java.util.function.UnaryOperator; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.MappingBuilder; @@ -28,6 +27,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; +import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.post; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; @@ -35,11 +35,13 @@ public class OpenAPIArrayMockService implements QuarkusTestResourceLifecycleManager { private static WireMockServer arrayService; + private static WireMockServer petService; @Override public Map start() { - arrayService = startServer("[1,2,3,4]", p -> p); - return Map.of("array-service-mock.url", arrayService.baseUrl()); + arrayService = startServer("[1,2,3,4]", post(urlEqualTo("/testArray")).withRequestBody(equalToJson("[1,2,3,4]"))); + petService = startServer("{\"name\":\"Maya\", \"photoUrls\":[]}", get(urlEqualTo("/pet/4"))); + return Map.of("array-service-mock.url", arrayService.baseUrl(), "petstore-service-mock.url", petService.baseUrl()); } @Override @@ -47,12 +49,15 @@ public void stop() { if (arrayService != null) { arrayService.stop(); } + if (petService != null) { + petService.stop(); + } } - private static WireMockServer startServer(final String response, UnaryOperator function) { + private static WireMockServer startServer(final String response, MappingBuilder mappingBuilder) { final WireMockServer server = new WireMockServer(options().dynamicPort()); server.start(); - server.stubFor(function.apply(post(urlEqualTo("/testArray")).withRequestBody(equalToJson("[1,2,3,4]"))) + server.stubFor(mappingBuilder .withPort(server.port()) .willReturn(aResponse() .withHeader("Content-Type", "application/json") 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-processes-persistence-common/src/main/resources/AddedTask.bpmn similarity index 99% rename from quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-quarkus-processes-infinispan/src/main/resources/AddedTask.bpmn2 rename to quarkus/integration-tests/integration-tests-quarkus-processes-persistence/integration-tests-processes-persistence-common/src/main/resources/AddedTask.bpmn index 6d8f8618cd1..6668aa4f716 100644 --- 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-processes-persistence-common/src/main/resources/AddedTask.bpmn @@ -16,7 +16,7 @@ - + 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 c0d75bce397..d9c94d5c5e7 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 @@ -32,6 +32,7 @@ import org.kie.kogito.AddressType; import org.kie.kogito.Person; import org.kie.kogito.Status; +import org.kie.kogito.usertask.model.TransitionInfo; import io.restassured.RestAssured; import io.restassured.http.ContentType; @@ -42,11 +43,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; 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; public abstract class PersistenceTest { + private static final String USER_TASK_BASE_PATH = "/usertasks/instance"; public static final Duration TIMEOUT = Duration.ofSeconds(10); public static final String PROCESS_ID = "hello"; @@ -59,6 +62,71 @@ public abstract class PersistenceTest { RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); } + @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("user", "mary") + .queryParam("group", "managers") + .body(new TransitionInfo("complete")) + .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)); + } + @Test void testPersistence() { Person person = new Person("Name", 10, BigDecimal.valueOf(5.0), Instant.now().truncatedTo(ChronoUnit.MILLIS), ZonedDateTime.now(ZoneOffset.UTC)); 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 cc668b06431..fd779ca5759 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,84 +18,9 @@ */ 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/TaskIT.java b/quarkus/integration-tests/integration-tests-quarkus-processes/src/test/java/org/kie/kogito/integrationtests/quarkus/TaskIT.java index 17cac753e50..0e5449be866 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 @@ -22,8 +22,8 @@ import java.io.InputStream; import java.net.URI; import java.util.Collections; +import java.util.Iterator; import java.util.Map; -import java.util.Set; import org.acme.travels.Traveller; import org.junit.jupiter.api.Test; @@ -307,7 +307,7 @@ void testCommentAndAttachment() { void testUpdateTaskInfo() { Traveller traveller = new Traveller("pepe", "rubiales", "pepe.rubiales@gmail.com", "Spanish"); - String processId = given() + given() .contentType(ContentType.JSON) .when() .body(Collections.singletonMap("traveller", traveller)) @@ -321,61 +321,52 @@ void testUpdateTaskInfo() { .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"); traveller.setEmail("javierito@gmail.com"); + TaskInfo upTaskInfo = new TaskInfo("firstAproval", "high", Collections.singleton("admin"), Collections.singleton("managers"), Collections.singleton("Javierito"), Collections.emptySet(), - Collections.emptySet(), Collections.singletonMap("traveller", traveller)); + Collections.emptySet(), Collections.emptyMap()); + given().contentType(ContentType.JSON) .when() .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) .body(upTaskInfo) - .put("/management/processes/approvals/instances/{processId}/tasks/{taskId}") + .put("/management/usertasks/{taskId}") .then() .statusCode(200); - ClientTaskInfo downTaskInfo = given().contentType(ContentType.JSON) + TaskInfo downTaskInfo = given().contentType(ContentType.JSON) .when() .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) - .get("/management/processes/approvals/instances/{processId}/tasks/{taskId}") + .get("/management/usertasks/{taskId}") .then() .statusCode(200) .extract() - .as(ClientTaskInfo.class); - assertEquals(traveller, downTaskInfo.inputParams.traveller); - } - - private static class ClientTaskInfo { - - public String description; - public String priority; - public Set potentialUsers; - public Set potentialGroups; - public Set excludedUsers; - public Set adminUsers; - public Set adminGroups; - public TravellerInputModel inputParams; + .as(TaskInfo.class); + + // we are only interested in our inputs + Iterator> iterator = downTaskInfo.getInputParams().entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry item = iterator.next(); + if (!upTaskInfo.getInputParams().keySet().contains(item.getKey())) { + iterator.remove(); + } + } + // we cannot compare yet because the json it is not properly deserialize + assertThat(downTaskInfo).isEqualTo(upTaskInfo); + assertThat(downTaskInfo.getInputParams()).isNotNull(); + assertThat(downTaskInfo.getInputParams().get("traveller")).isNull(); } - private static class TravellerInputModel { - public String TaskName; - public String NodeName; - public Boolean Skippable; - public String ActorId; - public String GroupId; - public Traveller traveller; - } } diff --git a/springboot/addons/task-management/src/main/java/org/kie/kogito/task/management/TaskManagementRestController.java b/springboot/addons/task-management/src/main/java/org/kie/kogito/task/management/TaskManagementRestController.java index aa82dc81cb4..8fdd0e8f6ff 100644 --- a/springboot/addons/task-management/src/main/java/org/kie/kogito/task/management/TaskManagementRestController.java +++ b/springboot/addons/task-management/src/main/java/org/kie/kogito/task/management/TaskManagementRestController.java @@ -20,12 +20,12 @@ import java.util.List; -import org.kie.kogito.jbpm.usertask.handler.Policies; import org.kie.kogito.process.ProcessConfig; -import org.kie.kogito.process.Processes; import org.kie.kogito.task.management.service.TaskInfo; import org.kie.kogito.task.management.service.TaskManagementOperations; import org.kie.kogito.task.management.service.TaskManagementService; +import org.kie.kogito.usertask.UserTaskConfig; +import org.kie.kogito.usertask.UserTasks; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -40,44 +40,41 @@ import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @RestController -@RequestMapping("/management/processes") +@RequestMapping("/management/usertasks/") public class TaskManagementRestController { TaskManagementOperations taskService; @Autowired - public TaskManagementRestController(Processes processes, ProcessConfig processConfig) { - this.taskService = new TaskManagementService(processes, processConfig); + public TaskManagementRestController(UserTasks userTasks, UserTaskConfig userTaskConfig, ProcessConfig processConfig) { + this.taskService = new TaskManagementService(userTasks, userTaskConfig, processConfig); } - @PutMapping(value = "{processId}/instances/{processInstanceId}/tasks/{taskId}", produces = APPLICATION_JSON_VALUE) - public ResponseEntity updateTask(@PathVariable("processId") String processId, - @PathVariable("processInstanceId") String processInstanceId, + @PutMapping(value = "{taskId}", produces = APPLICATION_JSON_VALUE) + public ResponseEntity updateTask( @PathVariable("taskId") String taskId, @RequestParam(value = "user", required = false) String user, @RequestParam(value = "group", required = false) List groups, @RequestBody TaskInfo taskInfo) { - taskService.updateTask(processId, processInstanceId, taskId, taskInfo, true, Policies.of(user, groups)); + taskService.updateTask(taskId, taskInfo, true); return ResponseEntity.ok().build(); } - @PatchMapping(value = "{processId}/instances/{processInstanceId}/tasks/{taskId}", produces = APPLICATION_JSON_VALUE) - public TaskInfo partialUpdateTask(@PathVariable("processId") String processId, - @PathVariable("processInstanceId") String processInstanceId, + @PatchMapping(value = "{taskId}", produces = APPLICATION_JSON_VALUE) + public TaskInfo partialUpdateTask( @PathVariable("taskId") String taskId, @RequestParam(value = "user", required = false) String user, @RequestParam(value = "group", required = false) List groups, @RequestBody TaskInfo taskInfo) { - return taskService.updateTask(processId, processInstanceId, taskId, taskInfo, false, Policies.of(user, groups)); + return taskService.updateTask(taskId, taskInfo, false); } - @GetMapping(value = "{processId}/instances/{processInstanceId}/tasks/{taskId}", produces = APPLICATION_JSON_VALUE) - public TaskInfo getTask(@PathVariable("processId") String processId, - @PathVariable("processInstanceId") String processInstanceId, + @GetMapping(value = "{taskId}", produces = APPLICATION_JSON_VALUE) + public TaskInfo getTask( @PathVariable("taskId") String taskId, @RequestParam(value = "user", required = false) String user, @RequestParam(value = "group", required = false) List groups) { - return taskService.getTask(processId, processInstanceId, taskId, Policies.of(user, groups)); + return taskService.getTask(taskId); } } 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 5da1e8e6cdf..f00a5267e0f 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 @@ -25,6 +25,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; +import java.util.Iterator; import java.util.Map; import java.util.stream.Stream; @@ -312,7 +313,7 @@ void testSaveTask() { void testUpdateTaskInfo() { Traveller traveller = new Traveller("pepe", "rubiales", "pepe.rubiales@gmail.com", "Spanish", new Address("Alfredo Di Stefano", "Madrid", "28033", "Spain")); - String processId = given() + given() .contentType(ContentType.JSON) .when() .body(Collections.singletonMap("traveller", traveller)) @@ -326,9 +327,8 @@ void testUpdateTaskInfo() { .contentType(ContentType.JSON) .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .when() - .get("/approvals/{processId}/tasks") + .get("/usertasks/instance") .then() .statusCode(200) .extract() @@ -341,10 +341,9 @@ void testUpdateTaskInfo() { .when() .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) .body(upTaskInfo) - .put("/management/processes/approvals/instances/{processId}/tasks/{taskId}") + .put("/management/usertasks/{taskId}") .then() .statusCode(200); @@ -352,13 +351,24 @@ void testUpdateTaskInfo() { .when() .queryParam("user", "admin") .queryParam("group", "managers") - .pathParam("processId", processId) .pathParam("taskId", taskId) - .get("/management/processes/approvals/instances/{processId}/tasks/{taskId}") + .get("/management/usertasks/{taskId}") .then() .statusCode(200) .extract() .as(TaskInfo.class); + + // we are only interested in our inputs + Iterator> iterator = downTaskInfo.getInputParams().entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry item = iterator.next(); + if (!upTaskInfo.getInputParams().keySet().contains(item.getKey())) { + iterator.remove(); + } + } + // we cannot compare yet because the json it is not properly deserialize + assertThat(downTaskInfo).isEqualTo(upTaskInfo); assertThat(downTaskInfo.getInputParams()).isNotNull(); + assertThat(downTaskInfo.getInputParams().get("traveller")).isNull(); } }