diff --git a/addons/common/jbpm-usertask-storage-jpa/pom.xml b/addons/common/jbpm-usertask-storage-jpa/pom.xml
new file mode 100644
index 00000000000..dd28be272d2
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/pom.xml
@@ -0,0 +1,73 @@
+
+
+ 4.0.0
+
+ org.kie
+ kogito-addons-common-parent
+ 999-SNAPSHOT
+
+
+ org.jbpm
+ jbpm-addons-usertask-storage-jpa
+
+ jBPM :: Add-Ons :: User Task Storage JPA :: Common
+ jBPM Add-Ons User Task Storage JPA Common
+
+
+ UTF-8
+ org.jbpm.usertask.storage.jpa
+
+
+
+
+ jakarta.persistence
+ jakarta.persistence-api
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.kie.kogito
+ kogito-api
+
+
+ org.kie.kogito
+ jbpm-usertask
+
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ test
+
+
+ ch.qos.logback
+ logback-classic
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/JPAUserTaskInstances.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/JPAUserTaskInstances.java
new file mode 100644
index 00000000000..d2f6a5e4cbf
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/JPAUserTaskInstances.java
@@ -0,0 +1,129 @@
+/*
+ * 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.usertask.jpa;
+
+import java.util.*;
+import java.util.function.Function;
+
+import org.jbpm.usertask.jpa.mapper.UserTaskInstanceEntityMapper;
+import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
+import org.jbpm.usertask.jpa.repository.UserTaskInstanceRepository;
+import org.kie.kogito.auth.IdentityProvider;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.UserTaskInstances;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class JPAUserTaskInstances implements UserTaskInstances {
+ public static final Logger LOGGER = LoggerFactory.getLogger(JPAUserTaskInstances.class);
+
+ private final UserTaskInstanceRepository userTaskInstanceRepository;
+ private final UserTaskInstanceEntityMapper userTaskInstanceEntityMapper;
+
+ private Function reconnectUserTaskInstance;
+ private Function disconnectUserTaskInstance;
+
+ public JPAUserTaskInstances(UserTaskInstanceRepository userTaskInstanceRepository, UserTaskInstanceEntityMapper userTaskInstanceEntityMapper) {
+ this.userTaskInstanceRepository = userTaskInstanceRepository;
+ this.userTaskInstanceEntityMapper = userTaskInstanceEntityMapper;
+ }
+
+ @Override
+ public Optional findById(String userTaskInstanceId) {
+ return this.userTaskInstanceRepository.findById(userTaskInstanceId)
+ .map(userTaskInstanceEntityMapper::mapTaskEntityToInstance)
+ .map(reconnectUserTaskInstance);
+ }
+
+ @Override
+ public List findByIdentity(IdentityProvider identityProvider) {
+ return userTaskInstanceRepository.findByIdentity(identityProvider)
+ .stream()
+ .map(userTaskInstanceEntityMapper::mapTaskEntityToInstance)
+ .map(reconnectUserTaskInstance)
+ .toList();
+ }
+
+ @Override
+ public boolean exists(String userTaskInstanceId) {
+ return userTaskInstanceRepository.findById(userTaskInstanceId).isPresent();
+ }
+
+ @Override
+ public UserTaskInstance create(UserTaskInstance userTaskInstance) {
+ Optional optional = userTaskInstanceRepository.findById(userTaskInstance.getId());
+
+ if (optional.isPresent()) {
+ LOGGER.error("Cannot create userTaskInstance with id {}. Task Already exists.", userTaskInstance.getId());
+ throw new IllegalArgumentException("Cannot create userTaskInstance with id " + userTaskInstance.getId() + ". Task Already exists.");
+ }
+
+ UserTaskInstanceEntity entity = new UserTaskInstanceEntity();
+ entity.setId(userTaskInstance.getId());
+
+ this.userTaskInstanceRepository.persist(entity);
+
+ userTaskInstanceEntityMapper.mapTaskInstanceToEntity(userTaskInstance, entity);
+
+ return this.reconnectUserTaskInstance.apply(userTaskInstance);
+ }
+
+ @Override
+ public UserTaskInstance update(UserTaskInstance userTaskInstance) {
+
+ Optional optional = userTaskInstanceRepository.findById(userTaskInstance.getId());
+
+ if (optional.isEmpty()) {
+ LOGGER.error("Could not find userTaskInstance with id {}", userTaskInstance.getId());
+ throw new RuntimeException("Could not find userTaskInstance with id " + userTaskInstance.getId());
+ }
+
+ UserTaskInstanceEntity userTaskInstanceEntity = optional.get();
+
+ userTaskInstanceEntityMapper.mapTaskInstanceToEntity(userTaskInstance, userTaskInstanceEntity);
+
+ userTaskInstanceRepository.update(userTaskInstanceEntity);
+
+ return userTaskInstance;
+ }
+
+ @Override
+ public UserTaskInstance remove(UserTaskInstance userTaskInstance) {
+ Optional optional = userTaskInstanceRepository.findById(userTaskInstance.getId());
+
+ if (optional.isEmpty()) {
+ LOGGER.warn("Could not remove userTaskInstance with id {}, task cannot be found", userTaskInstance.getId());
+ throw new RuntimeException("Could not remove userTaskInstance with id " + userTaskInstance.getId() + ", userTaskInstance cannot be found");
+ }
+
+ this.userTaskInstanceRepository.remove(optional.get());
+ return this.disconnectUserTaskInstance.apply(userTaskInstance);
+ }
+
+ @Override
+ public void setReconnectUserTaskInstance(Function reconnectUserTaskInstance) {
+ this.reconnectUserTaskInstance = reconnectUserTaskInstance;
+ }
+
+ @Override
+ public void setDisconnectUserTaskInstance(Function disconnectUserTaskInstance) {
+ this.disconnectUserTaskInstance = disconnectUserTaskInstance;
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/AttachmentsEntityMapper.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/AttachmentsEntityMapper.java
new file mode 100644
index 00000000000..210553ed853
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/AttachmentsEntityMapper.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.mapper;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.jbpm.usertask.jpa.model.AttachmentEntity;
+import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
+import org.jbpm.usertask.jpa.repository.AttachmentRepository;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
+import org.kie.kogito.usertask.model.Attachment;
+
+import static java.util.stream.Collectors.toCollection;
+
+public class AttachmentsEntityMapper {
+ private final AttachmentRepository repository;
+
+ public AttachmentsEntityMapper(AttachmentRepository repository) {
+ this.repository = repository;
+ }
+
+ public void mapInstanceToEntity(UserTaskInstance userTaskInstance, UserTaskInstanceEntity userTaskInstanceEntity) {
+ Collection toRemove = userTaskInstanceEntity.getAttachments()
+ .stream()
+ .filter(entity -> userTaskInstance.getAttachments().stream().noneMatch(attachment -> attachment.getId().equals(entity.getId())))
+ .toList();
+
+ toRemove.forEach(attachment -> {
+ repository.remove(attachment);
+ userTaskInstanceEntity.removeAttachment(attachment);
+ });
+
+ userTaskInstance.getAttachments().forEach(attachment -> {
+ AttachmentEntity attachmentEntity = userTaskInstanceEntity.getAttachments().stream().filter(entity -> entity.getId().equals(attachment.getId())).findFirst().orElseGet(() -> {
+ AttachmentEntity entity = new AttachmentEntity();
+ userTaskInstanceEntity.addAttachment(entity);
+ return entity;
+ });
+ attachmentEntity.setId(attachment.getId());
+ attachmentEntity.setUpdatedBy(attachment.getUpdatedBy());
+ attachmentEntity.setName(attachment.getName());
+ attachmentEntity.setUrl(attachment.getContent().toString());
+ attachmentEntity.setUpdatedAt(attachment.getUpdatedAt());
+ });
+ }
+
+ public void mapEntityToInstance(UserTaskInstanceEntity userTaskInstanceEntity, UserTaskInstance userTaskInstance) {
+
+ List attachments = userTaskInstanceEntity.getAttachments().stream().map(attachmentEntity -> {
+ Attachment attachment = new Attachment(attachmentEntity.getId(), attachmentEntity.getUpdatedBy());
+ attachment.setId(attachmentEntity.getId());
+ attachment.setName(attachmentEntity.getName());
+ attachment.setContent(URI.create(attachmentEntity.getUrl()));
+ attachment.setUpdatedAt(attachmentEntity.getUpdatedAt());
+ return attachment;
+ }).collect(toCollection(ArrayList::new));
+
+ ((DefaultUserTaskInstance) userTaskInstance).setAttachments(attachments);
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/CommentsEntityMapper.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/CommentsEntityMapper.java
new file mode 100644
index 00000000000..b854ec3510a
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/CommentsEntityMapper.java
@@ -0,0 +1,78 @@
+/*
+ * 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.usertask.jpa.mapper;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.jbpm.usertask.jpa.model.CommentEntity;
+import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
+import org.jbpm.usertask.jpa.repository.CommentRepository;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
+import org.kie.kogito.usertask.model.Comment;
+
+import static java.util.stream.Collectors.toCollection;
+
+public class CommentsEntityMapper {
+
+ private final CommentRepository repository;
+
+ public CommentsEntityMapper(CommentRepository repository) {
+ this.repository = repository;
+ }
+
+ public void mapInstanceToEntity(UserTaskInstance userTaskInstance, UserTaskInstanceEntity userTaskInstanceEntity) {
+ Collection toRemove = userTaskInstanceEntity.getComments()
+ .stream()
+ .filter(entity -> userTaskInstance.getComments().stream().noneMatch(comment -> comment.getId().equals(entity.getId())))
+ .toList();
+
+ toRemove.forEach(comment -> {
+ repository.remove(comment);
+ userTaskInstanceEntity.removeComment(comment);
+ });
+
+ userTaskInstance.getComments().forEach(comment -> {
+ CommentEntity commentEntity = userTaskInstanceEntity.getComments().stream().filter(entity -> entity.getId().equals(comment.getId())).findFirst().orElseGet(() -> {
+ CommentEntity entity = new CommentEntity();
+ userTaskInstanceEntity.addComment(entity);
+ return entity;
+ });
+ commentEntity.setId(comment.getId());
+ commentEntity.setUpdatedBy(comment.getUpdatedBy());
+ commentEntity.setComment(comment.getContent());
+ commentEntity.setUpdatedAt(comment.getUpdatedAt());
+ });
+ }
+
+ public void mapEntityToInstance(UserTaskInstanceEntity userTaskInstanceEntity, UserTaskInstance userTaskInstance) {
+ List comments = userTaskInstanceEntity.getComments().stream().map(commentEntity -> {
+ Comment comment = new Comment(commentEntity.getId(), commentEntity.getUpdatedBy());
+ comment.setId(commentEntity.getId());
+ comment.setContent(commentEntity.getComment());
+ comment.setUpdatedAt(commentEntity.getUpdatedAt());
+ return comment;
+ }).collect(toCollection(ArrayList::new));
+
+ ((DefaultUserTaskInstance) userTaskInstance).setComments(comments);
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskInputsEntityMapper.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskInputsEntityMapper.java
new file mode 100644
index 00000000000..12a6c50f986
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskInputsEntityMapper.java
@@ -0,0 +1,77 @@
+/*
+ * 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.usertask.jpa.mapper;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import org.jbpm.usertask.jpa.mapper.json.utils.JSONUtils;
+import org.jbpm.usertask.jpa.model.TaskInputEntity;
+import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
+import org.jbpm.usertask.jpa.repository.TaskInputRepository;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
+
+public class TaskInputsEntityMapper {
+
+ private TaskInputRepository repository;
+
+ public TaskInputsEntityMapper(TaskInputRepository repository) {
+ this.repository = repository;
+ }
+
+ public void mapInstanceToEntity(UserTaskInstance userTaskInstance, UserTaskInstanceEntity userTaskInstanceEntity) {
+ Collection toRemove = userTaskInstanceEntity.getInputs()
+ .stream()
+ .filter(entity -> !userTaskInstance.getInputs().containsKey(entity.getName()))
+ .toList();
+
+ toRemove.forEach(input -> {
+ repository.remove(input);
+ userTaskInstanceEntity.removeInput(input);
+ });
+
+ userTaskInstance.getInputs().forEach((key, value) -> {
+ TaskInputEntity inputEntity = userTaskInstanceEntity.getInputs().stream().filter(entity -> entity.getName().equals(key)).findFirst().orElseGet(() -> {
+ TaskInputEntity entity = new TaskInputEntity();
+ entity.setName(key);
+ userTaskInstanceEntity.addInput(entity);
+ return entity;
+ });
+ inputEntity.setName(key);
+ if (Objects.nonNull(value)) {
+ inputEntity.setValue(JSONUtils.valueToString(value).getBytes(StandardCharsets.UTF_8));
+ inputEntity.setJavaType(value.getClass().getName());
+ }
+ });
+ }
+
+ public void mapEntityToInstance(UserTaskInstanceEntity userTaskInstanceEntity, UserTaskInstance userTaskInstance) {
+ Map inputs = new HashMap<>();
+ userTaskInstanceEntity.getInputs().forEach(taskInputEntity -> {
+ String value = taskInputEntity.getValue() == null ? null : new String(taskInputEntity.getValue(), StandardCharsets.UTF_8);
+ inputs.put(taskInputEntity.getName(), JSONUtils.stringTreeToValue(value, taskInputEntity.getJavaType()));
+ });
+ ((DefaultUserTaskInstance) userTaskInstance).setInputs(inputs);
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskMetadataEntityMapper.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskMetadataEntityMapper.java
new file mode 100644
index 00000000000..999c7357496
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskMetadataEntityMapper.java
@@ -0,0 +1,74 @@
+/*
+ * 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.usertask.jpa.mapper;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import org.jbpm.usertask.jpa.mapper.json.utils.JSONUtils;
+import org.jbpm.usertask.jpa.model.TaskMetadataEntity;
+import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
+import org.jbpm.usertask.jpa.repository.TaskMetadataRepository;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
+
+public class TaskMetadataEntityMapper {
+
+ private final TaskMetadataRepository repository;
+
+ public TaskMetadataEntityMapper(TaskMetadataRepository repository) {
+ this.repository = repository;
+ }
+
+ public void mapInstanceToEntity(UserTaskInstance userTaskInstance, UserTaskInstanceEntity userTaskInstanceEntity) {
+ Collection toRemove = userTaskInstanceEntity.getMetadata()
+ .stream()
+ .filter(entity -> !userTaskInstance.getMetadata().containsKey(entity.getName()))
+ .toList();
+
+ toRemove.forEach(metadata -> {
+ repository.remove(metadata);
+ userTaskInstanceEntity.removeMetadata(metadata);
+ });
+
+ userTaskInstance.getMetadata().forEach((key, value) -> {
+ TaskMetadataEntity metadataEntity = userTaskInstanceEntity.getMetadata().stream().filter(entity -> entity.getName().equals(key)).findFirst().orElseGet(() -> {
+ TaskMetadataEntity entity = new TaskMetadataEntity();
+ userTaskInstanceEntity.addMetadata(entity);
+ return entity;
+ });
+ metadataEntity.setName(key);
+ if (Objects.nonNull(value)) {
+ metadataEntity.setValue(JSONUtils.valueToString(value));
+ metadataEntity.setJavaType(value.getClass().getName());
+ }
+ });
+ }
+
+ public void mapEntityToInstance(UserTaskInstanceEntity userTaskInstanceEntity, UserTaskInstance userTaskInstance) {
+ Map metadata = new HashMap<>();
+ userTaskInstanceEntity.getMetadata().forEach(metadataEntry -> {
+ metadata.put(metadataEntry.getName(), JSONUtils.stringTreeToValue(metadataEntry.getValue(), metadataEntry.getJavaType()));
+ });
+ ((DefaultUserTaskInstance) userTaskInstance).setMetadata(metadata);
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskOutputsEntityMapper.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskOutputsEntityMapper.java
new file mode 100644
index 00000000000..1fa7d4ae282
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/TaskOutputsEntityMapper.java
@@ -0,0 +1,77 @@
+/*
+ * 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.usertask.jpa.mapper;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import org.jbpm.usertask.jpa.mapper.json.utils.JSONUtils;
+import org.jbpm.usertask.jpa.model.TaskOutputEntity;
+import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
+import org.jbpm.usertask.jpa.repository.TaskOutputRepository;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
+
+public class TaskOutputsEntityMapper {
+
+ private final TaskOutputRepository repository;
+
+ public TaskOutputsEntityMapper(TaskOutputRepository repository) {
+ this.repository = repository;
+ }
+
+ public void mapInstanceToEntity(UserTaskInstance userTaskInstance, UserTaskInstanceEntity userTaskInstanceEntity) {
+ Collection toRemove = userTaskInstanceEntity.getOutputs()
+ .stream()
+ .filter(entity -> !userTaskInstance.getOutputs().containsKey(entity.getName()))
+ .toList();
+
+ toRemove.forEach(output -> {
+ repository.remove(output);
+ userTaskInstanceEntity.removeOutput(output);
+ });
+
+ userTaskInstance.getOutputs().forEach((key, value) -> {
+ TaskOutputEntity outputEntity = userTaskInstanceEntity.getOutputs().stream().filter(entity -> entity.getName().equals(key)).findFirst().orElseGet(() -> {
+ TaskOutputEntity entity = new TaskOutputEntity();
+ entity.setName(key);
+ userTaskInstanceEntity.addOutput(entity);
+ return entity;
+ });
+ outputEntity.setName(key);
+ if (Objects.nonNull(value)) {
+ outputEntity.setValue(JSONUtils.valueToString(value).getBytes(StandardCharsets.UTF_8));
+ outputEntity.setJavaType(value.getClass().getName());
+ }
+ });
+ }
+
+ public void mapEntityToInstance(UserTaskInstanceEntity userTaskInstanceEntity, UserTaskInstance userTaskInstance) {
+ Map outputs = new HashMap<>();
+ userTaskInstanceEntity.getOutputs().forEach(taskOutputEntity -> {
+ String value = taskOutputEntity.getValue() == null ? null : new String(taskOutputEntity.getValue(), StandardCharsets.UTF_8);
+ outputs.put(taskOutputEntity.getName(), JSONUtils.stringTreeToValue(value, taskOutputEntity.getJavaType()));
+ });
+ ((DefaultUserTaskInstance) userTaskInstance).setOutputs(outputs);
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/UserTaskInstanceEntityMapper.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/UserTaskInstanceEntityMapper.java
new file mode 100644
index 00000000000..e03125742ba
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/UserTaskInstanceEntityMapper.java
@@ -0,0 +1,101 @@
+/*
+ * 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.usertask.jpa.mapper;
+
+import java.util.Set;
+
+import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
+import org.kie.kogito.usertask.lifecycle.UserTaskState;
+
+public class UserTaskInstanceEntityMapper {
+
+ private final AttachmentsEntityMapper attachmentsMapper;
+ private final CommentsEntityMapper commentMapper;
+ private final TaskInputsEntityMapper taskInputsMapper;
+ private final TaskOutputsEntityMapper taskOutputsMapper;
+ private final TaskMetadataEntityMapper taskMetadataMapper;
+
+ public UserTaskInstanceEntityMapper(AttachmentsEntityMapper attachmentsMapper, CommentsEntityMapper commentsMapper, TaskMetadataEntityMapper taskMetadataMapper,
+ TaskInputsEntityMapper taskInputsMapper, TaskOutputsEntityMapper taskOutputMapper) {
+ this.attachmentsMapper = attachmentsMapper;
+ this.commentMapper = commentsMapper;
+ this.taskMetadataMapper = taskMetadataMapper;
+ this.taskInputsMapper = taskInputsMapper;
+ this.taskOutputsMapper = taskOutputMapper;
+ }
+
+ public UserTaskInstanceEntity mapTaskInstanceToEntity(UserTaskInstance userTaskInstance, UserTaskInstanceEntity entity) {
+ entity.setId(userTaskInstance.getId());
+ entity.setTaskName(userTaskInstance.getTaskName());
+ entity.setTaskDescription(userTaskInstance.getTaskDescription());
+ entity.setTaskPriority(userTaskInstance.getTaskPriority());
+ entity.setStatus(userTaskInstance.getStatus().getName());
+ entity.setTerminationType(userTaskInstance.getStatus().getTerminate() == null ? null : userTaskInstance.getStatus().getTerminate().name());
+ entity.setExternalReferenceId(userTaskInstance.getExternalReferenceId());
+ entity.setUserTaskId(userTaskInstance.getUserTaskId());
+
+ entity.setActualOwner(userTaskInstance.getActualOwner());
+ entity.setPotentialUsers(Set.copyOf(userTaskInstance.getPotentialUsers()));
+ entity.setPotentialGroups(Set.copyOf(userTaskInstance.getPotentialGroups()));
+ entity.setAdminUsers(Set.copyOf(userTaskInstance.getAdminUsers()));
+ entity.setAdminGroups(Set.copyOf(userTaskInstance.getAdminGroups()));
+ entity.setExcludedUsers(Set.copyOf(userTaskInstance.getExcludedUsers()));
+
+ attachmentsMapper.mapInstanceToEntity(userTaskInstance, entity);
+ commentMapper.mapInstanceToEntity(userTaskInstance, entity);
+ taskInputsMapper.mapInstanceToEntity(userTaskInstance, entity);
+ taskOutputsMapper.mapInstanceToEntity(userTaskInstance, entity);
+ taskMetadataMapper.mapInstanceToEntity(userTaskInstance, entity);
+
+ return entity;
+ }
+
+ public UserTaskInstance mapTaskEntityToInstance(UserTaskInstanceEntity entity) {
+
+ DefaultUserTaskInstance instance = new DefaultUserTaskInstance();
+
+ instance.setId(entity.getId());
+ instance.setUserTaskId(entity.getUserTaskId());
+ instance.setExternalReferenceId(entity.getExternalReferenceId());
+ instance.setTaskName(entity.getTaskName());
+ instance.setTaskDescription(entity.getTaskDescription());
+ instance.setTaskPriority(entity.getTaskPriority());
+
+ UserTaskState.TerminationType terminationType = entity.getTerminationType() == null ? null : UserTaskState.TerminationType.valueOf(entity.getTerminationType());
+ instance.setStatus(UserTaskState.of(entity.getStatus(), terminationType));
+
+ instance.setActualOwner(entity.getActualOwner());
+ instance.setPotentialUsers(Set.copyOf(entity.getPotentialUsers()));
+ instance.setPotentialGroups(Set.copyOf(entity.getPotentialGroups()));
+ instance.setAdminUsers(Set.copyOf(entity.getAdminUsers()));
+ instance.setAdminGroups(Set.copyOf(entity.getAdminGroups()));
+ instance.setExcludedUsers(Set.copyOf(entity.getExcludedUsers()));
+
+ attachmentsMapper.mapEntityToInstance(entity, instance);
+ commentMapper.mapEntityToInstance(entity, instance);
+ taskInputsMapper.mapEntityToInstance(entity, instance);
+ taskOutputsMapper.mapEntityToInstance(entity, instance);
+ taskMetadataMapper.mapEntityToInstance(entity, instance);
+
+ return instance;
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/json/utils/JSONUtils.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/json/utils/JSONUtils.java
new file mode 100644
index 00000000000..b7e33589800
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/mapper/json/utils/JSONUtils.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.mapper.json.utils;
+
+import java.util.Objects;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+
+public class JSONUtils {
+
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+ static {
+ OBJECT_MAPPER.registerModule(new JavaTimeModule());
+ }
+
+ public static String valueToString(Object value) {
+ try {
+ return OBJECT_MAPPER.writeValueAsString(value);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static Object stringTreeToValue(String value, String javaType) {
+ try {
+ if (Objects.isNull(value) || Objects.isNull(javaType)) {
+ return null;
+ }
+ return OBJECT_MAPPER.readValue(value, Class.forName(javaType));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/AttachmentEntity.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/AttachmentEntity.java
new file mode 100644
index 00000000000..a0bff107682
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/AttachmentEntity.java
@@ -0,0 +1,95 @@
+/*
+ * 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.usertask.jpa.model;
+
+import java.util.Date;
+
+import jakarta.persistence.*;
+
+@Entity
+@Table(name = "jbpm_user_tasks_attachments")
+public class AttachmentEntity {
+
+ @Id
+ private String id;
+
+ private String name;
+
+ private String url;
+
+ @Column(name = "updated_by")
+ private String updatedBy;
+
+ @Temporal(TemporalType.TIMESTAMP)
+ @Column(name = "updated_at")
+ private Date updatedAt;
+
+ @ManyToOne
+ @JoinColumn(name = "task_id", foreignKey = @ForeignKey(name = "fk_user_task_attachment_tid"))
+ private UserTaskInstanceEntity taskInstance;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public Date getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public void setUpdatedAt(Date updatedAt) {
+ this.updatedAt = updatedAt;
+ }
+
+ public String getUpdatedBy() {
+ return updatedBy;
+ }
+
+ public void setUpdatedBy(String updatedBy) {
+ this.updatedBy = updatedBy;
+ }
+
+ public UserTaskInstanceEntity getTaskInstance() {
+ return taskInstance;
+ }
+
+ public void setTaskInstance(UserTaskInstanceEntity taskInstance) {
+ this.taskInstance = taskInstance;
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/CommentEntity.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/CommentEntity.java
new file mode 100644
index 00000000000..cf70f4e7bca
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/CommentEntity.java
@@ -0,0 +1,85 @@
+/*
+ * 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.usertask.jpa.model;
+
+import java.util.Date;
+
+import jakarta.persistence.*;
+
+@Entity
+@Table(name = "jbpm_user_tasks_comments")
+public class CommentEntity {
+
+ @Id
+ private String id;
+
+ private String comment;
+
+ @Column(name = "updated_by")
+ private String updatedBy;
+
+ @Temporal(TemporalType.TIMESTAMP)
+ @Column(name = "updated_at")
+ private Date updatedAt;
+
+ @ManyToOne(optional = false)
+ @JoinColumn(name = "task_id", foreignKey = @ForeignKey(name = "fk_user_task_comment_tid"))
+ private UserTaskInstanceEntity taskInstance;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getComment() {
+ return comment;
+ }
+
+ public void setComment(String comment) {
+ this.comment = comment;
+ }
+
+ public Date getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public void setUpdatedAt(Date updatedAt) {
+ this.updatedAt = updatedAt;
+ }
+
+ public String getUpdatedBy() {
+ return updatedBy;
+ }
+
+ public void setUpdatedBy(String user) {
+ this.updatedBy = user;
+ }
+
+ public UserTaskInstanceEntity getTaskInstance() {
+ return taskInstance;
+ }
+
+ public void setTaskInstance(UserTaskInstanceEntity taskInstance) {
+ this.taskInstance = taskInstance;
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntity.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntity.java
new file mode 100644
index 00000000000..506f3bf925b
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntity.java
@@ -0,0 +1,91 @@
+/*
+ * 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.usertask.jpa.model;
+
+import java.util.Objects;
+
+import jakarta.persistence.*;
+
+@MappedSuperclass
+public abstract class TaskDataEntity {
+
+ @Id
+ @Column(name = "name")
+ protected String name;
+
+ @Id
+ @ManyToOne(optional = false)
+ @JoinColumn(name = "task_id")
+ protected UserTaskInstanceEntity taskInstance;
+
+ @Column(name = "value")
+ protected T value;
+
+ @Column(name = "java_type")
+ protected String javaType;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public UserTaskInstanceEntity getTaskInstance() {
+ return taskInstance;
+ }
+
+ public void setTaskInstance(UserTaskInstanceEntity taskInstance) {
+ this.taskInstance = taskInstance;
+ }
+
+ public T getValue() {
+ return value;
+ }
+
+ public void setValue(T value) {
+ this.value = value;
+ }
+
+ public String getJavaType() {
+ return javaType;
+ }
+
+ public void setJavaType(String javaType) {
+ this.javaType = javaType;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ TaskDataEntity> that = (TaskDataEntity>) o;
+ return Objects.equals(getName(), that.getName()) && Objects.equals(getTaskInstance(), that.getTaskInstance()) && Objects.equals(getValue(),
+ that.getValue()) && Objects.equals(getJavaType(), that.getJavaType());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getName(), getTaskInstance(), getValue(), getJavaType());
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntityPK.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntityPK.java
new file mode 100644
index 00000000000..4d0f1fab9ed
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskDataEntityPK.java
@@ -0,0 +1,76 @@
+/*
+ * 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.usertask.jpa.model;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+public class TaskDataEntityPK implements Serializable {
+
+ private String name;
+ private UserTaskInstanceEntity taskInstance;
+
+ public TaskDataEntityPK() {
+ }
+
+ public TaskDataEntityPK(String inputName, UserTaskInstanceEntity taskInstance) {
+ this.taskInstance = taskInstance;
+ this.name = inputName;
+ }
+
+ public UserTaskInstanceEntity getTaskInstance() {
+ return taskInstance;
+ }
+
+ public void setTaskInstance(UserTaskInstanceEntity taskInstance) {
+ this.taskInstance = taskInstance;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ TaskDataEntityPK that = (TaskDataEntityPK) o;
+ return Objects.equals(getName(), that.getName()) && Objects.equals(getTaskInstance(), that.getTaskInstance());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getName(), getTaskInstance());
+ }
+
+ @Override
+ public String toString() {
+ return "TaskInputEntityId{" +
+ "taskInstance='" + taskInstance + '\'' +
+ ", name='" + name + '\'' +
+ '}';
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskInputEntity.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskInputEntity.java
new file mode 100644
index 00000000000..9c4351cfd24
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskInputEntity.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.model;
+
+import jakarta.persistence.*;
+
+@Entity
+@Table(name = "jbpm_user_tasks_inputs")
+@AttributeOverrides({
+ @AttributeOverride(name = "name", column = @Column(name = "input_name")),
+ @AttributeOverride(name = "value", column = @Column(name = "input_value"))
+})
+@AssociationOverride(name = "taskInstance", foreignKey = @ForeignKey(name = "jbpm_user_tasks_inputs_tid"))
+@IdClass(TaskDataEntityPK.class)
+public class TaskInputEntity extends TaskDataEntity {
+
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskMetadataEntity.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskMetadataEntity.java
new file mode 100644
index 00000000000..2729ee68716
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskMetadataEntity.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.model;
+
+import jakarta.persistence.*;
+
+@Entity
+@Table(name = "jbpm_user_tasks_metadata")
+@AttributeOverrides({
+ @AttributeOverride(name = "name", column = @Column(name = "metadata_name")),
+ @AttributeOverride(name = "value", column = @Column(name = "metadata_value"))
+})
+@AssociationOverride(name = "taskInstance",
+ joinColumns = @JoinColumn(name = "task_id", foreignKey = @ForeignKey(name = "jbpm_user_tasks_metadata_tid")))
+@IdClass(TaskDataEntityPK.class)
+public class TaskMetadataEntity extends TaskDataEntity {
+
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskOutputEntity.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskOutputEntity.java
new file mode 100644
index 00000000000..bd7098f2450
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/TaskOutputEntity.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.model;
+
+import jakarta.persistence.*;
+
+@Entity
+@Table(name = "jbpm_user_tasks_outputs")
+@AttributeOverrides({
+ @AttributeOverride(name = "name", column = @Column(name = "output_name")),
+ @AttributeOverride(name = "value", column = @Column(name = "output_value"))
+})
+@AssociationOverride(name = "taskInstance",
+ joinColumns = @JoinColumn(name = "task_id", foreignKey = @ForeignKey(name = "jbpm_user_tasks_outputs_tid")))
+@IdClass(TaskDataEntityPK.class)
+public class TaskOutputEntity extends TaskDataEntity {
+
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/UserTaskInstanceEntity.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/UserTaskInstanceEntity.java
new file mode 100644
index 00000000000..8b3cfca0282
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/model/UserTaskInstanceEntity.java
@@ -0,0 +1,334 @@
+/*
+ * 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.usertask.jpa.model;
+
+import java.util.*;
+
+import jakarta.persistence.*;
+
+@Entity
+@NamedQuery(name = UserTaskInstanceEntity.GET_INSTANCES_BY_IDENTITY,
+ query = "select userTask from UserTaskInstanceEntity userTask " +
+ "left join userTask.adminGroups adminGroups " +
+ "left join userTask.potentialGroups potentialGroups " +
+ "where userTask.actualOwner = :userId " +
+ "or :userId member of userTask.adminUsers " +
+ "or adminGroups in (:roles) " +
+ "or (:userId member of userTask.potentialUsers and :userId not member of userTask.excludedUsers) " +
+ "or potentialGroups in (:roles)")
+@Table(name = "jbpm_user_tasks")
+public class UserTaskInstanceEntity {
+ public static final String GET_INSTANCES_BY_IDENTITY = "UserTaskInstanceEntity.GetInstanceByIdentity";
+
+ @Id
+ private String id;
+
+ @Column(name = "user_task_id")
+ private String userTaskId;
+
+ @Column(name = "task_name")
+ private String taskName;
+
+ @Column(name = "task_description")
+ private String taskDescription;
+
+ @Column(name = "task_priority")
+ private String taskPriority;
+
+ private String status;
+
+ @Column(name = "termination_type")
+ private String terminationType;
+
+ @Column(name = "actual_owner")
+ private String actualOwner;
+
+ @Column(name = "external_reference_id")
+ private String externalReferenceId;
+
+ @ElementCollection
+ @CollectionTable(name = "jbpm_user_tasks_potential_users", joinColumns = @JoinColumn(name = "task_id", foreignKey = @ForeignKey(name = "fk_jbpm_user_tasks_potential_users_tid")))
+ @Column(name = "user_id", nullable = false)
+ private Set potentialUsers = new HashSet<>();
+
+ @ElementCollection
+ @CollectionTable(name = "jbpm_user_tasks_potential_groups", joinColumns = @JoinColumn(name = "task_id"),
+ foreignKey = @ForeignKey(name = "fk_jbpm_user_tasks_potential_groups_tid"))
+ @Column(name = "group_id")
+ private Set potentialGroups = new HashSet<>();
+
+ @ElementCollection
+ @CollectionTable(name = "jbpm_user_tasks_admin_users", joinColumns = @JoinColumn(name = "task_id", foreignKey = @ForeignKey(name = "fk_jbpm_user_tasks_admin_users_tid")))
+ @Column(name = "user_id", nullable = false)
+ private Set adminUsers = new HashSet<>();
+
+ @ElementCollection
+ @CollectionTable(name = "jbpm_user_tasks_admin_groups", joinColumns = @JoinColumn(name = "task_id"),
+ foreignKey = @ForeignKey(name = "fk_jbpm_user_tasks_admin_groups_tid"))
+ @Column(name = "group_id")
+ private Set adminGroups = new HashSet<>();
+
+ @ElementCollection
+ @CollectionTable(name = "jbpm_user_tasks_excluded_users", joinColumns = @JoinColumn(name = "task_id", foreignKey = @ForeignKey(name = "fk_jbpm_user_tasks_excluded_users_tid")))
+ @Column(name = "user_id", nullable = false)
+ private Set excludedUsers = new HashSet<>();
+
+ @OneToMany(mappedBy = "taskInstance", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
+ private List attachments = new ArrayList<>();
+
+ @OneToMany(mappedBy = "taskInstance", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
+ private List comments = new ArrayList<>();
+
+ @OneToMany(mappedBy = "taskInstance", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
+ private List inputs = new ArrayList<>();
+
+ @OneToMany(mappedBy = "taskInstance", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
+ private List outputs = new ArrayList<>();
+
+ @OneToMany(mappedBy = "taskInstance", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
+ private List metadata = new ArrayList<>();
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getActualOwner() {
+ return actualOwner;
+ }
+
+ public void setActualOwner(String actualOwner) {
+ this.actualOwner = actualOwner;
+ }
+
+ public String getTaskName() {
+ return taskName;
+ }
+
+ public void setTaskName(String taskName) {
+ this.taskName = taskName;
+ }
+
+ public String getTaskDescription() {
+ return taskDescription;
+ }
+
+ public void setTaskDescription(String taskDescription) {
+ this.taskDescription = taskDescription;
+ }
+
+ public String getTaskPriority() {
+ return taskPriority;
+ }
+
+ public void setTaskPriority(String taskPriority) {
+ this.taskPriority = taskPriority;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public String getExternalReferenceId() {
+ return externalReferenceId;
+ }
+
+ public void setExternalReferenceId(String externalReferenceId) {
+ this.externalReferenceId = externalReferenceId;
+ }
+
+ public void setPotentialUsers(Set potentialUsers) {
+ this.potentialUsers.clear();
+ this.potentialUsers.addAll(potentialUsers);
+ }
+
+ public Set getPotentialUsers() {
+ return potentialUsers;
+ }
+
+ public Set getPotentialGroups() {
+ return potentialGroups;
+ }
+
+ public void setPotentialGroups(Set potentialGroups) {
+ this.potentialGroups.clear();
+ this.potentialGroups.addAll(potentialGroups);
+ }
+
+ public Set getAdminUsers() {
+ return adminUsers;
+ }
+
+ public void setAdminUsers(Set adminUsers) {
+ this.adminUsers.clear();
+ this.adminUsers.addAll(adminUsers);
+ }
+
+ public Collection getAdminGroups() {
+ return adminGroups;
+ }
+
+ public void setAdminGroups(Set adminGroups) {
+ this.adminGroups.clear();
+ this.adminGroups.addAll(adminGroups);
+ }
+
+ public Collection getExcludedUsers() {
+ return excludedUsers;
+ }
+
+ public void setExcludedUsers(Set excludedUsers) {
+ this.excludedUsers.clear();
+ this.excludedUsers.addAll(excludedUsers);
+ }
+
+ public void clearAttachments() {
+ this.attachments.clear();
+ }
+
+ public Collection getAttachments() {
+ return attachments;
+ }
+
+ public void addAttachment(AttachmentEntity attachment) {
+ attachment.setTaskInstance(this);
+ this.attachments.add(attachment);
+ }
+
+ public void removeAttachment(AttachmentEntity attachmentEntity) {
+ this.attachments.remove(attachmentEntity);
+ }
+
+ public void setAttachments(Collection attachments) {
+ this.clearAttachments();
+ this.attachments.addAll(attachments);
+ }
+
+ public void clearComments() {
+ this.comments.clear();
+ }
+
+ public void removeComment(CommentEntity comment) {
+ this.comments.remove(comment);
+ }
+
+ public Collection getComments() {
+ return comments;
+ }
+
+ public void addComment(CommentEntity comment) {
+ comment.setTaskInstance(this);
+ this.comments.add(comment);
+ }
+
+ public void setComments(Collection comments) {
+ this.clearComments();
+ this.comments.addAll(comments);
+ }
+
+ public void clearInputs() {
+ this.inputs.clear();
+ }
+
+ public Collection getInputs() {
+ return inputs;
+ }
+
+ public void setInputs(Collection inputs) {
+ this.clearInputs();
+ this.inputs.addAll(inputs);
+ }
+
+ public void addInput(TaskInputEntity input) {
+ input.setTaskInstance(this);
+ this.inputs.add(input);
+ }
+
+ public void removeInput(TaskInputEntity input) {
+ this.inputs.remove(input);
+ }
+
+ public void clearOutputs() {
+ this.outputs.clear();
+ }
+
+ public Collection getOutputs() {
+ return outputs;
+ }
+
+ public void addOutput(TaskOutputEntity output) {
+ output.setTaskInstance(this);
+ this.outputs.add(output);
+ }
+
+ public void removeOutput(TaskOutputEntity output) {
+ this.outputs.remove(output);
+ }
+
+ public void setOutputs(Collection outputs) {
+ this.clearOutputs();
+ this.outputs.addAll(outputs);
+ }
+
+ public void clearMetadata() {
+ this.metadata.clear();
+ }
+
+ public Collection getMetadata() {
+ return metadata;
+ }
+
+ public void addMetadata(TaskMetadataEntity metadata) {
+ metadata.setTaskInstance(this);
+ this.metadata.add(metadata);
+ }
+
+ public void removeMetadata(TaskMetadataEntity metadata) {
+ this.metadata.remove(metadata);
+ }
+
+ public void setMetadata(Collection metadata) {
+ this.clearMetadata();
+ this.metadata.addAll(metadata);
+ }
+
+ public void setUserTaskId(String userTaskId) {
+ this.userTaskId = userTaskId;
+ }
+
+ public String getUserTaskId() {
+ return userTaskId;
+ }
+
+ public void setTerminationType(String terminationType) {
+ this.terminationType = terminationType;
+ }
+
+ public String getTerminationType() {
+ return terminationType;
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/AttachmentRepository.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/AttachmentRepository.java
new file mode 100644
index 00000000000..92bf5f5a81f
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/AttachmentRepository.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.repository;
+
+import org.jbpm.usertask.jpa.model.AttachmentEntity;
+
+public class AttachmentRepository extends BaseRepository {
+
+ public AttachmentRepository(UserTaskJPAContext context) {
+ super(context);
+ }
+
+ @Override
+ public Class getEntityClass() {
+ return AttachmentEntity.class;
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/BaseRepository.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/BaseRepository.java
new file mode 100644
index 00000000000..ed144ddbc04
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/BaseRepository.java
@@ -0,0 +1,63 @@
+/*
+ * 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.usertask.jpa.repository;
+
+import java.util.List;
+import java.util.Optional;
+
+import jakarta.persistence.EntityManager;
+
+public abstract class BaseRepository {
+
+ protected UserTaskJPAContext context;
+
+ public BaseRepository(UserTaskJPAContext context) {
+ this.context = context;
+ }
+
+ public Optional findById(K id) {
+ return Optional.ofNullable(getEntityManager().find(getEntityClass(), id));
+ }
+
+ public List findAll() {
+ return getEntityManager().createQuery("from " + getEntityClass().getName(), getEntityClass()).getResultList();
+ }
+
+ public T persist(T entity) {
+ getEntityManager().persist(entity);
+
+ return entity;
+ }
+
+ public T update(T entity) {
+ return this.getEntityManager().merge(entity);
+ }
+
+ public T remove(T entity) {
+ this.getEntityManager().remove(entity);
+ return entity;
+ }
+
+ public abstract Class getEntityClass();
+
+ protected EntityManager getEntityManager() {
+ return context.getEntityManager();
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/CommentRepository.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/CommentRepository.java
new file mode 100644
index 00000000000..98b85d6a991
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/CommentRepository.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.repository;
+
+import org.jbpm.usertask.jpa.model.CommentEntity;
+
+public class CommentRepository extends BaseRepository {
+
+ public CommentRepository(UserTaskJPAContext context) {
+ super(context);
+ }
+
+ @Override
+ public Class getEntityClass() {
+ return CommentEntity.class;
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
new file mode 100644
index 00000000000..6e85081ff76
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskInputRepository.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.repository;
+
+import org.jbpm.usertask.jpa.model.TaskDataEntityPK;
+import org.jbpm.usertask.jpa.model.TaskInputEntity;
+
+public class TaskInputRepository extends BaseRepository {
+
+ public TaskInputRepository(UserTaskJPAContext context) {
+ super(context);
+ }
+
+ @Override
+ public Class getEntityClass() {
+ return TaskInputEntity.class;
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskMetadataRepository.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskMetadataRepository.java
new file mode 100644
index 00000000000..26796bb38aa
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskMetadataRepository.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.repository;
+
+import org.jbpm.usertask.jpa.model.TaskDataEntityPK;
+import org.jbpm.usertask.jpa.model.TaskMetadataEntity;
+
+public class TaskMetadataRepository extends BaseRepository {
+
+ public TaskMetadataRepository(UserTaskJPAContext context) {
+ super(context);
+ }
+
+ @Override
+ public Class getEntityClass() {
+ return TaskMetadataEntity.class;
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskOutputRepository.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskOutputRepository.java
new file mode 100644
index 00000000000..2b39b8f1bb0
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/TaskOutputRepository.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.repository;
+
+import org.jbpm.usertask.jpa.model.TaskDataEntityPK;
+import org.jbpm.usertask.jpa.model.TaskOutputEntity;
+
+public class TaskOutputRepository extends BaseRepository {
+
+ public TaskOutputRepository(UserTaskJPAContext context) {
+ super(context);
+ }
+
+ @Override
+ public Class getEntityClass() {
+ return TaskOutputEntity.class;
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/UserTaskInstanceRepository.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/UserTaskInstanceRepository.java
new file mode 100644
index 00000000000..1be6a4aec71
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/UserTaskInstanceRepository.java
@@ -0,0 +1,48 @@
+/*
+ * 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.usertask.jpa.repository;
+
+import java.util.List;
+
+import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
+import org.kie.kogito.auth.IdentityProvider;
+
+import jakarta.persistence.TypedQuery;
+
+import static org.jbpm.usertask.jpa.model.UserTaskInstanceEntity.GET_INSTANCES_BY_IDENTITY;
+
+public class UserTaskInstanceRepository extends BaseRepository {
+
+ public UserTaskInstanceRepository(UserTaskJPAContext context) {
+ super(context);
+ }
+
+ public List findByIdentity(IdentityProvider identityProvider) {
+ TypedQuery query = getEntityManager().createNamedQuery(GET_INSTANCES_BY_IDENTITY, UserTaskInstanceEntity.class);
+ query.setParameter("userId", identityProvider.getName());
+ query.setParameter("roles", identityProvider.getRoles());
+ return query.getResultList();
+ }
+
+ @Override
+ public Class getEntityClass() {
+ return UserTaskInstanceEntity.class;
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/UserTaskJPAContext.java b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/UserTaskJPAContext.java
new file mode 100644
index 00000000000..8606184dd58
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/repository/UserTaskJPAContext.java
@@ -0,0 +1,27 @@
+/*
+ * 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.usertask.jpa.repository;
+
+import jakarta.persistence.EntityManager;
+
+public interface UserTaskJPAContext {
+
+ EntityManager getEntityManager();
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/resources/META-INF/beans.xml b/addons/common/jbpm-usertask-storage-jpa/src/main/resources/META-INF/beans.xml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/resources/META-INF/kie-flyway.properties b/addons/common/jbpm-usertask-storage-jpa/src/main/resources/META-INF/kie-flyway.properties
new file mode 100644
index 00000000000..67ce94390d9
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/resources/META-INF/kie-flyway.properties
@@ -0,0 +1,23 @@
+#
+# 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.
+#
+
+module.name=jbpm-user-task-storage
+
+module.locations.h2=classpath:kie-flyway/db/user-tasks/h2
+module.locations.postgresql=classpath:kie-flyway/db/user-tasks/postgresql
\ No newline at end of file
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/resources/kie-flyway/db/user-tasks/h2/V1.0.0__jBPM_user_task_create.sql b/addons/common/jbpm-usertask-storage-jpa/src/main/resources/kie-flyway/db/user-tasks/h2/V1.0.0__jBPM_user_task_create.sql
new file mode 100644
index 00000000000..058f3599d3b
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/resources/kie-flyway/db/user-tasks/h2/V1.0.0__jBPM_user_task_create.sql
@@ -0,0 +1,166 @@
+/*
+ * 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.
+ */
+
+create table jbpm_user_tasks (
+ id varchar(50) not null,
+ user_task_id varchar(255),
+ task_priority varchar(50),
+ actual_owner varchar(255),
+ task_description varchar(255),
+ status varchar(255),
+ termination_type varchar(255),
+ external_reference_id varchar(255),
+ task_name varchar(255),
+ primary key (id)
+);
+
+create table jbpm_user_tasks_potential_users(
+ task_id varchar(50) not null,
+ user_id varchar(255) not null,
+ primary key (task_id, user_id)
+);
+
+create table jbpm_user_tasks_potential_groups(
+ task_id varchar(50) not null,
+ group_id varchar(255) not null,
+ primary key (task_id, group_id)
+);
+
+create table jbpm_user_tasks_admin_users (
+ task_id varchar(50) not null,
+ user_id varchar(255) not null,
+ primary key (task_id, user_id)
+);
+
+create table jbpm_user_tasks_admin_groups (
+ task_id varchar(50) not null,
+ group_id varchar(255) not null,
+ primary key (task_id, group_id)
+);
+
+create table jbpm_user_tasks_excluded_users(
+ task_id varchar(50) not null,
+ user_id varchar(255) not null,
+ primary key (task_id, user_id)
+);
+
+create table jbpm_user_tasks_attachments (
+ id varchar(50) not null,
+ name varchar(255),
+ updated_by varchar(255),
+ updated_at timestamp,
+ url varchar(255),
+ task_id varchar(50) not null,
+ primary key (id)
+);
+
+create table jbpm_user_tasks_comments (
+ id varchar(50) not null,
+ updated_by varchar(255),
+ updated_at timestamp,
+ comment varchar(255),
+ task_id varchar(50) not null,
+ primary key (id)
+);
+
+create table jbpm_user_tasks_inputs (
+ task_id varchar(50) not null,
+ input_name varchar(255) not null,
+ input_value varbinary(max),
+ java_type varchar(255),
+ primary key (task_id, input_name)
+);
+
+create table jbpm_user_tasks_outputs (
+ task_id varchar(50) not null,
+ output_name varchar(255) not null,
+ output_value varbinary(max),
+ java_type varchar(255),
+ primary key (task_id, output_name)
+);
+
+create table jbpm_user_tasks_metadata (
+ task_id varchar(50) not null,
+ metadata_name varchar(255) not null,
+ metadata_value varchar(512),
+ java_type varchar(255),
+ primary key (task_id, metadata_name)
+);
+
+alter table if exists jbpm_user_tasks_potential_users
+drop constraint if exists fk_jbpm_user_tasks_potential_users_tid cascade;
+
+alter table if exists jbpm_user_tasks_potential_users
+add constraint fk_jbpm_user_fk_tasks_potential_users_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_potential_groups
+drop constraint if exists fk_jbpm_user_tasks_potential_groups_tid cascade;
+
+alter table if exists jbpm_user_tasks_potential_groups
+add constraint fk_jbpm_user_tasks_potential_groups_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_tasks_admin_users
+drop constraint if exists fk_jbpm_user_tasks_admin_users_tid cascade;
+
+alter table if exists jbpm_user_tasks_admin_users
+add constraint fk_jbpm_user_tasks_admin_users_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_tasks_admin_groups
+drop constraint if exists fk_jbpm_user_tasks_admin_groups_tid cascade;
+
+alter table if exists jbpm_user_tasks_admin_groups
+add constraint fk_jbpm_user_tasks_admin_groups_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_tasks_excluded_users
+drop constraint if exists fk_jbpm_user_tasks_excluded_users_tid cascade;
+
+alter table if exists jbpm_user_tasks_excluded_users
+add constraint fk_jbpm_user_tasks_excluded_users_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_tasks_attachments
+drop constraint if exists fk_user_task_attachment_tid cascade;
+
+alter table if exists jbpm_user_tasks_attachments
+add constraint fk_user_task_attachment_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_tasks_comments
+drop constraint if exists fk_user_task_comment_tid cascade;
+
+alter table if exists jbpm_user_tasks_comments
+add constraint fk_user_task_comment_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_tasks_inputs
+drop constraint if exists fk_jbpm_user_tasks_inputs_tid cascade;
+
+alter table if exists jbpm_user_tasks_inputs
+add constraint fk_jbpm_user_tasks_inputs_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_tasks_outputs
+drop constraint if exists fk_jbpm_user_tasks_outputs_tid cascade;
+
+alter table if exists jbpm_user_tasks_outputs
+add constraint fk_jbpm_user_tasks_outputs_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_tasks_metadata
+drop constraint if exists fk_jbpm_user_tasks_metadata_tid cascade;
+
+alter table if exists jbpm_user_tasks_metadata
+add constraint fk_jbpm_user_tasks_metadata_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/main/resources/kie-flyway/db/user-tasks/postgresql/V1.0.0__jBPM_user_task_create.sql b/addons/common/jbpm-usertask-storage-jpa/src/main/resources/kie-flyway/db/user-tasks/postgresql/V1.0.0__jBPM_user_task_create.sql
new file mode 100644
index 00000000000..8daa0437216
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/main/resources/kie-flyway/db/user-tasks/postgresql/V1.0.0__jBPM_user_task_create.sql
@@ -0,0 +1,164 @@
+/*
+ * 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.
+ */
+
+create table jbpm_user_tasks (
+ id varchar(50) not null,
+ user_task_id varchar(255),
+ task_priority varchar(50),
+ actual_owner varchar(255),
+ task_description varchar(255),
+ status varchar(255),
+ termination_type varchar(255),
+ external_reference_id varchar(255),
+ task_name varchar(255),
+ primary key (id)
+);
+
+create table jbpm_user_tasks_potential_users(
+ task_id varchar(50) not null,
+ user_id varchar(255) not null,
+ primary key (task_id, user_id)
+);
+
+create table jbpm_user_tasks_potential_groups(
+ task_id varchar(50) not null,
+ group_id varchar(255) not null,
+ primary key (task_id, group_id)
+);
+
+create table jbpm_user_tasks_admin_users (
+ task_id varchar(50) not null,
+ user_id varchar(255) not null,
+ primary key (task_id, user_id)
+);
+
+create table jbpm_user_tasks_admin_groups (
+ task_id varchar(50) not null,
+ group_id varchar(255) not null,
+ primary key (task_id, group_id)
+);
+
+create table jbpm_user_tasks_excluded_users(
+ task_id varchar(50) not null,
+ user_id varchar(255) not null,
+ primary key (task_id, user_id)
+);
+
+create table jbpm_user_tasks_attachments (
+ id varchar(50) not null,
+ name varchar(255),
+ updated_by varchar(255),
+ updated_at timestamp(6),
+ url varchar(255),
+ task_id varchar(50) not null,
+ primary key (id)
+);
+
+create table jbpm_user_tasks_comments (
+ id varchar(50) not null,
+ updated_by varchar(255),
+ updated_at timestamp(6),
+ comment varchar(255),
+ task_id varchar(50) not null,
+ primary key (id)
+);
+
+create table jbpm_user_tasks_inputs (
+ task_id varchar(50) not null,
+ input_name varchar(255) not null,
+ input_value bytea,
+ java_type varchar(255),
+ primary key (task_id, input_name)
+);
+
+create table jbpm_user_tasks_outputs (
+ task_id varchar(50) not null,
+ output_name varchar(255) not null,
+ output_value bytea,
+ java_type varchar(255),
+ primary key (task_id, output_name)
+);
+
+create table jbpm_user_tasks_metadata (
+ task_id varchar(50),
+ metadata_name varchar(255) not null,
+ metadata_value varchar(512),
+ java_type varchar(255),
+ primary key (task_id, metadata_name)
+);
+
+alter table if exists jbpm_user_tasks_potential_users
+drop constraint if exists fk_jbpm_user_tasks_potential_users_tid cascade;
+
+alter table if exists jbpm_user_tasks_potential_users
+add constraint fk_jbpm_user_fk_tasks_potential_users_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_potential_groups
+drop constraint if exists fk_jbpm_user_tasks_potential_groups_tid cascade;
+
+alter table if exists jbpm_user_tasks_potential_groups
+add constraint fk_jbpm_user_tasks_potential_groups_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_tasks_admin_users
+drop constraint if exists fk_jbpm_user_tasks_admin_users_tid cascade;
+
+alter table if exists jbpm_user_tasks_admin_users
+add constraint fk_jbpm_user_tasks_admin_users_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_tasks_admin_groups
+drop constraint if exists fk_jbpm_user_tasks_admin_groups_tid cascade;
+
+alter table if exists jbpm_user_tasks_admin_groups
+add constraint fk_jbpm_user_tasks_admin_groups_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_tasks_excluded_users
+drop constraint if exists fk_jbpm_user_tasks_excluded_users_tid cascade;
+
+alter table if exists jbpm_user_tasks_excluded_users
+add constraint fk_jbpm_user_tasks_excluded_users_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_tasks_attachments
+drop constraint if exists fk_user_task_attachment_tid cascade;
+
+alter table if exists jbpm_user_tasks_attachments
+add constraint fk_user_task_attachment_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_tasks_comments
+drop constraint if exists fk_user_task_comment_tid cascade;
+
+alter table if exists jbpm_user_tasks_comments
+add constraint fk_user_task_comment_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_tasks_inputs
+drop constraint if exists fk_jbpm_user_tasks_inputs_tid cascade;
+
+alter table if exists jbpm_user_tasks_inputs
+add constraint fk_jbpm_user_tasks_inputs_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_tasks_outputs
+drop constraint if exists fk_jbpm_user_tasks_outputs_tid cascade;
+
+alter table if exists jbpm_user_tasks_outputs
+add constraint fk_jbpm_user_tasks_outputs_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
+
+alter table if exists jbpm_user_tasks_metadata
+drop constraint if exists fk_jbpm_user_tasks_metadata_tid cascade;
+
+alter table if exists jbpm_user_tasks_metadata
+add constraint fk_jbpm_user_tasks_metadata_tid foreign key (task_id) references jbpm_user_tasks(id) on delete cascade;
\ No newline at end of file
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/JPAUserTaskInstancesTest.java b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/JPAUserTaskInstancesTest.java
new file mode 100644
index 00000000000..d0ca49f1143
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/JPAUserTaskInstancesTest.java
@@ -0,0 +1,220 @@
+/*
+ * 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.usertask.jpa;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+
+import org.assertj.core.api.Assertions;
+import org.jbpm.usertask.jpa.mapper.*;
+import org.jbpm.usertask.jpa.mapper.utils.TestUtils;
+import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
+import org.jbpm.usertask.jpa.repository.UserTaskInstanceRepository;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.kie.kogito.auth.IdentityProviders;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class JPAUserTaskInstancesTest {
+
+ @Mock
+ private UserTaskInstanceRepository userTaskInstanceRepository;
+
+ @Mock
+ private AttachmentsEntityMapper attachmentsEntityMapper;
+ @Mock
+ private CommentsEntityMapper commentsEntityMapper;
+ @Mock
+ private TaskMetadataEntityMapper metadataEntityMapper;
+ @Mock
+ private TaskInputsEntityMapper inputsEntityMapper;
+ @Mock
+ private TaskOutputsEntityMapper outputsEntityMapper;
+
+ private UserTaskInstanceEntityMapper userTaskInstanceEntityMapper;
+ @Mock
+ private Function reconnectUserTaskInstance;
+ @Mock
+ private Function disconnectUserTaskInstance;
+
+ private JPAUserTaskInstances jpaUserTaskInstances;
+
+ @BeforeEach
+ public void setup() {
+ userTaskInstanceEntityMapper = spy(new UserTaskInstanceEntityMapper(attachmentsEntityMapper, commentsEntityMapper, metadataEntityMapper, inputsEntityMapper, outputsEntityMapper));
+ jpaUserTaskInstances = new JPAUserTaskInstances(userTaskInstanceRepository, userTaskInstanceEntityMapper);
+ jpaUserTaskInstances.setReconnectUserTaskInstance(reconnectUserTaskInstance);
+ jpaUserTaskInstances.setDisconnectUserTaskInstance(disconnectUserTaskInstance);
+ }
+
+ @Test
+ public void testSuccessfulFindById() {
+ Optional result = Optional.of(TestUtils.createUserTaskInstanceEntity());
+
+ when(userTaskInstanceRepository.findById(any())).thenReturn(result);
+
+ jpaUserTaskInstances.findById("1234");
+
+ verify(userTaskInstanceEntityMapper, times(1)).mapTaskEntityToInstance(any());
+ verify(reconnectUserTaskInstance, times(1)).apply(any());
+ }
+
+ @Test
+ public void testUnSuccessfulFindById() {
+ when(userTaskInstanceRepository.findById(any())).thenReturn(Optional.empty());
+
+ jpaUserTaskInstances.findById("1234");
+
+ verify(userTaskInstanceEntityMapper, never()).mapTaskEntityToInstance(any());
+ verify(reconnectUserTaskInstance, never()).apply(any());
+ }
+
+ @Test
+ public void testSuccessfulExists() {
+ Optional result = Optional.of(TestUtils.createUserTaskInstanceEntity());
+
+ when(userTaskInstanceRepository.findById(any())).thenReturn(result);
+
+ Assertions.assertThat(jpaUserTaskInstances.exists("1234"))
+ .isTrue();
+
+ verify(userTaskInstanceEntityMapper, never()).mapTaskEntityToInstance(any());
+ verify(reconnectUserTaskInstance, never()).apply(any());
+ }
+
+ @Test
+ public void testUnSuccessfulExists() {
+ when(userTaskInstanceRepository.findById(any())).thenReturn(Optional.empty());
+
+ Assertions.assertThat(jpaUserTaskInstances.exists("1234"))
+ .isFalse();
+
+ verify(userTaskInstanceEntityMapper, never()).mapTaskEntityToInstance(any());
+ verify(reconnectUserTaskInstance, never()).apply(any());
+ }
+
+ @Test
+ public void testSuccessfulFindByIdentity() {
+ List result = List.of(TestUtils.createUserTaskInstanceEntity(), TestUtils.createUserTaskInstanceEntity());
+
+ when(userTaskInstanceRepository.findByIdentity(any())).thenReturn(result);
+
+ List instances = jpaUserTaskInstances.findByIdentity(IdentityProviders.of("user", "group"));
+
+ Assertions.assertThat(instances)
+ .hasSize(2);
+
+ verify(userTaskInstanceEntityMapper, times(2)).mapTaskEntityToInstance(any());
+ verify(reconnectUserTaskInstance, times(2)).apply(any());
+
+ }
+
+ @Test
+ public void testUnSuccessfulFindByIdentity() {
+ when(userTaskInstanceRepository.findByIdentity(any())).thenReturn(List.of());
+
+ List instances = jpaUserTaskInstances.findByIdentity(IdentityProviders.of("user", "group"));
+
+ Assertions.assertThat(instances)
+ .isEmpty();
+
+ verify(userTaskInstanceEntityMapper, never()).mapTaskEntityToInstance(any());
+ verify(reconnectUserTaskInstance, never()).apply(any());
+ }
+
+ @Test
+ public void testSuccessfulCreate() {
+ when(userTaskInstanceRepository.findById(any())).thenReturn(Optional.empty());
+
+ jpaUserTaskInstances.create(TestUtils.createUserTaskInstance());
+
+ verify(userTaskInstanceRepository, times(1)).persist(any());
+ verify(userTaskInstanceEntityMapper, times(1)).mapTaskInstanceToEntity(any(), any());
+ verify(reconnectUserTaskInstance, times(1)).apply(any());
+ }
+
+ @Test
+ public void testUnSuccessfulCreate() {
+ Optional result = Optional.of(TestUtils.createUserTaskInstanceEntity());
+ when(userTaskInstanceRepository.findById(any())).thenReturn(result);
+
+ Assertions.assertThatThrownBy(() -> {
+ jpaUserTaskInstances.create(TestUtils.createUserTaskInstance());
+ }).hasMessageContaining("Task Already exists.");
+
+ verify(userTaskInstanceRepository, never()).persist(any());
+ verify(userTaskInstanceEntityMapper, never()).mapTaskInstanceToEntity(any(), any());
+ verify(reconnectUserTaskInstance, never()).apply(any());
+ }
+
+ @Test
+ public void testSuccessfulUpdate() {
+ Optional result = Optional.of(TestUtils.createUserTaskInstanceEntity());
+ when(userTaskInstanceRepository.findById(any())).thenReturn(result);
+
+ jpaUserTaskInstances.update(TestUtils.createUserTaskInstance());
+
+ verify(userTaskInstanceRepository, times(1)).update(any());
+ verify(userTaskInstanceEntityMapper, times(1)).mapTaskInstanceToEntity(any(), any());
+ }
+
+ @Test
+ public void testUnSuccessfulUpdate() {
+ when(userTaskInstanceRepository.findById(any())).thenReturn(Optional.empty());
+
+ Assertions.assertThatThrownBy(() -> {
+ jpaUserTaskInstances.update(TestUtils.createUserTaskInstance());
+ }).hasMessageContaining("Could not find userTaskInstance with id ");
+
+ verify(userTaskInstanceRepository, never()).persist(any());
+ verify(userTaskInstanceEntityMapper, never()).mapTaskInstanceToEntity(any(), any());
+ }
+
+ @Test
+ public void testSuccessfulRemove() {
+ Optional result = Optional.of(TestUtils.createUserTaskInstanceEntity());
+ when(userTaskInstanceRepository.findById(any())).thenReturn(result);
+
+ jpaUserTaskInstances.remove(TestUtils.createUserTaskInstance());
+
+ verify(userTaskInstanceRepository, times(1)).remove(any());
+ verify(disconnectUserTaskInstance, times(1)).apply(any());
+ }
+
+ @Test
+ public void testUnSuccessfulRemove() {
+ when(userTaskInstanceRepository.findById(any())).thenReturn(Optional.empty());
+
+ Assertions.assertThatThrownBy(() -> {
+ jpaUserTaskInstances.remove(TestUtils.createUserTaskInstance());
+ }).hasMessageContaining("Could not remove userTaskInstance with id");
+
+ verify(userTaskInstanceRepository, never()).persist(any());
+ verify(disconnectUserTaskInstance, never()).apply(any());
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/AttachmentsEntityMapperTest.java b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/AttachmentsEntityMapperTest.java
new file mode 100644
index 00000000000..cafdb8e166b
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/AttachmentsEntityMapperTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.usertask.jpa.mapper;
+
+import java.net.URI;
+import java.util.Date;
+
+import org.assertj.core.api.Assertions;
+import org.jbpm.usertask.jpa.mapper.utils.TestUtils;
+import org.jbpm.usertask.jpa.model.AttachmentEntity;
+import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
+import org.jbpm.usertask.jpa.repository.AttachmentRepository;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.model.Attachment;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class AttachmentsEntityMapperTest {
+
+ @Mock
+ private AttachmentRepository repository;
+ private AttachmentsEntityMapper mapper;
+
+ @BeforeEach
+ public void setup() {
+ mapper = new AttachmentsEntityMapper(repository);
+ }
+
+ @Test
+ public void testMapAttachmentsFromInstanceToEntity() {
+ UserTaskInstance instance = TestUtils.createUserTaskInstance();
+ UserTaskInstanceEntity entity = new UserTaskInstanceEntity();
+
+ Attachment attachment = new Attachment("1", "John");
+ attachment.setName("attachment 1");
+ attachment.setContent(URI.create("http://localhost:8080/my-attachment.txt"));
+ attachment.setUpdatedAt(new Date());
+
+ Attachment attachment2 = new Attachment("2", "Ned");
+ attachment2.setName("attachment 2");
+ attachment2.setContent(URI.create("http://localhost:8080/my-attachment2.txt"));
+ attachment2.setUpdatedAt(new Date());
+
+ instance.addAttachment(attachment);
+ instance.addAttachment(attachment2);
+
+ mapper.mapInstanceToEntity(instance, entity);
+
+ Assertions.assertThat(entity.getAttachments())
+ .hasSize(2);
+ verify(repository, never())
+ .remove(any());
+ TestUtils.assertUserTaskEntityAttachments(entity.getAttachments(), instance.getAttachments());
+
+ instance.removeAttachment(attachment);
+ mapper.mapInstanceToEntity(instance, entity);
+
+ verify(repository, times(1))
+ .remove(any());
+ Assertions.assertThat(entity.getAttachments())
+ .hasSize(1);
+ TestUtils.assertUserTaskEntityAttachments(entity.getAttachments(), instance.getAttachments());
+
+ instance.removeAttachment(attachment2);
+ mapper.mapInstanceToEntity(instance, entity);
+
+ verify(repository, times(2))
+ .remove(any());
+ Assertions.assertThat(entity.getAttachments())
+ .hasSize(0);
+ }
+
+ @Test
+ public void testMapAttachmentsFromEntityToInstance() {
+
+ UserTaskInstanceEntity entity = new UserTaskInstanceEntity();
+
+ UserTaskInstance instance = TestUtils.createUserTaskInstance();
+
+ AttachmentEntity attachment = new AttachmentEntity();
+ attachment.setId("1");
+ attachment.setUpdatedBy("John");
+ attachment.setName("attachment 1");
+ attachment.setUrl("http://localhost:8080/my-attachment.txt");
+ attachment.setUpdatedAt(new Date());
+
+ AttachmentEntity attachment2 = new AttachmentEntity();
+ attachment2.setId("2");
+ attachment2.setUpdatedBy("Ned");
+ attachment2.setName("attachment 2");
+ attachment2.setUrl("http://localhost:8080/my-attachment_2.txt");
+ attachment2.setUpdatedAt(new Date());
+
+ entity.addAttachment(attachment);
+ entity.addAttachment(attachment2);
+
+ mapper.mapEntityToInstance(entity, instance);
+
+ Assertions.assertThat(instance.getAttachments())
+ .hasSize(2);
+ verify(repository, never())
+ .remove(any());
+ TestUtils.assertUserTaskInstanceAttachments(instance.getAttachments(), entity.getAttachments());
+
+ entity.removeAttachment(attachment);
+ mapper.mapEntityToInstance(entity, instance);
+
+ Assertions.assertThat(entity.getAttachments())
+ .hasSize(1);
+ TestUtils.assertUserTaskInstanceAttachments(instance.getAttachments(), entity.getAttachments());
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/CommentsEntityMapperTest.java b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/CommentsEntityMapperTest.java
new file mode 100644
index 00000000000..62477c2d7ab
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/CommentsEntityMapperTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.usertask.jpa.mapper;
+
+import java.util.Date;
+
+import org.assertj.core.api.Assertions;
+import org.jbpm.usertask.jpa.mapper.utils.TestUtils;
+import org.jbpm.usertask.jpa.model.CommentEntity;
+import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
+import org.jbpm.usertask.jpa.repository.CommentRepository;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.model.Comment;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class CommentsEntityMapperTest {
+
+ @Mock
+ private CommentRepository repository;
+ private CommentsEntityMapper mapper;
+
+ @BeforeEach
+ public void setup() {
+ mapper = new CommentsEntityMapper(repository);
+ }
+
+ @Test
+ public void testMapCommentsFromInstanceToEntity() {
+ UserTaskInstance instance = TestUtils.createUserTaskInstance();
+ UserTaskInstanceEntity entity = new UserTaskInstanceEntity();
+
+ Comment comment = new Comment("1", "John");
+ comment.setContent("This is comment 1");
+ comment.setUpdatedAt(new Date());
+
+ Comment comment2 = new Comment("2", "Ned");
+ comment2.setContent("This is comment 2");
+ comment2.setUpdatedAt(new Date());
+
+ instance.addComment(comment);
+ instance.addComment(comment2);
+
+ mapper.mapInstanceToEntity(instance, entity);
+
+ Assertions.assertThat(entity.getComments())
+ .hasSize(2);
+ verify(repository, never())
+ .remove(any());
+
+ TestUtils.assertUserTaskEntityComments(entity.getComments(), instance.getComments());
+
+ instance.removeComment(comment);
+ mapper.mapInstanceToEntity(instance, entity);
+
+ verify(repository, times(1))
+ .remove(any());
+ Assertions.assertThat(entity.getComments())
+ .hasSize(1);
+ TestUtils.assertUserTaskEntityComments(entity.getComments(), instance.getComments());
+
+ instance.removeComment(comment2);
+ mapper.mapInstanceToEntity(instance, entity);
+
+ verify(repository, times(2))
+ .remove(any());
+ Assertions.assertThat(entity.getComments())
+ .hasSize(0);
+ }
+
+ @Test
+ public void testMapCommentsFromEntityToInstance() {
+
+ UserTaskInstanceEntity entity = new UserTaskInstanceEntity();
+
+ UserTaskInstance instance = TestUtils.createUserTaskInstance();
+
+ CommentEntity comment = new CommentEntity();
+ comment.setId("1");
+ comment.setUpdatedBy("John");
+ comment.setComment("This is comment 1");
+ comment.setUpdatedAt(new Date());
+
+ CommentEntity comment2 = new CommentEntity();
+ comment2.setId("2");
+ comment2.setUpdatedBy("Ned");
+ comment2.setComment("This is comment 2");
+ comment2.setUpdatedAt(new Date());
+
+ entity.addComment(comment);
+ entity.addComment(comment2);
+
+ mapper.mapEntityToInstance(entity, instance);
+
+ Assertions.assertThat(instance.getComments())
+ .hasSize(2);
+ verify(repository, never())
+ .remove(any());
+
+ TestUtils.assertUserTaskInstanceComments(instance.getComments(), entity.getComments());
+
+ entity.removeComment(comment);
+ mapper.mapEntityToInstance(entity, instance);
+
+ Assertions.assertThat(entity.getComments())
+ .hasSize(1);
+ TestUtils.assertUserTaskInstanceComments(instance.getComments(), entity.getComments());
+
+ entity.removeComment(comment);
+ mapper.mapEntityToInstance(entity, instance);
+
+ Assertions.assertThat(entity.getComments())
+ .hasSize(1);
+ TestUtils.assertUserTaskInstanceComments(instance.getComments(), entity.getComments());
+
+ entity.removeComment(comment2);
+ mapper.mapEntityToInstance(entity, instance);
+
+ Assertions.assertThat(entity.getComments())
+ .hasSize(0);
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/TaskInputsEntityMapperTest.java b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/TaskInputsEntityMapperTest.java
new file mode 100644
index 00000000000..1aaeed39d30
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/TaskInputsEntityMapperTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.usertask.jpa.mapper;
+
+import java.nio.charset.StandardCharsets;
+
+import org.assertj.core.api.Assertions;
+import org.jbpm.usertask.jpa.mapper.utils.TestUtils;
+import org.jbpm.usertask.jpa.model.TaskInputEntity;
+import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
+import org.jbpm.usertask.jpa.repository.TaskInputRepository;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class TaskInputsEntityMapperTest {
+
+ @Mock
+ private TaskInputRepository repository;
+ private TaskInputsEntityMapper mapper;
+
+ @BeforeEach
+ public void setup() {
+ mapper = new TaskInputsEntityMapper(repository);
+ }
+
+ @Test
+ public void testMapInputsFromInstanceToEntity() {
+ UserTaskInstance instance = TestUtils.createUserTaskInstance();
+ UserTaskInstanceEntity entity = new UserTaskInstanceEntity();
+
+ mapper.mapInstanceToEntity(instance, entity);
+
+ Assertions.assertThat(entity.getInputs())
+ .hasSize(8);
+ verify(repository, never())
+ .remove(any());
+ TestUtils.assertUserTaskEntityInputs(entity, instance);
+
+ instance.getInputs().remove("in_string");
+ instance.getInputs().remove("in_integer");
+ instance.getInputs().remove("in_null");
+
+ mapper.mapInstanceToEntity(instance, entity);
+
+ Assertions.assertThat(entity.getInputs())
+ .hasSize(5);
+ verify(repository, times(3))
+ .remove(any());
+
+ TestUtils.assertUserTaskEntityInputs(entity, instance);
+
+ instance.getInputs().clear();
+
+ mapper.mapInstanceToEntity(instance, entity);
+
+ Assertions.assertThat(entity.getInputs())
+ .hasSize(0);
+ verify(repository, times(8))
+ .remove(any());
+ }
+
+ @Test
+ public void testMapInputsFromEntityToInstance() {
+ final String stringValue = "This is the input value";
+
+ UserTaskInstance instance = TestUtils.createUserTaskInstance();
+ UserTaskInstanceEntity entity = new UserTaskInstanceEntity();
+
+ TaskInputEntity input = new TaskInputEntity();
+ input.setName("in_string");
+ input.setValue(stringValue.getBytes(StandardCharsets.UTF_8));
+
+ entity.addInput(input);
+
+ mapper.mapEntityToInstance(entity, instance);
+
+ Assertions.assertThat(instance.getInputs())
+ .hasSize(1);
+
+ TestUtils.assertUserTaskInstanceInputs(instance, entity);
+
+ entity.getInputs().clear();
+
+ mapper.mapEntityToInstance(entity, instance);
+
+ Assertions.assertThat(instance.getInputs())
+ .hasSize(0);
+ }
+
+ @Test
+ public void testMappingRoundCircle() {
+
+ UserTaskInstance instance = TestUtils.createUserTaskInstance();
+ UserTaskInstanceEntity entity = new UserTaskInstanceEntity();
+
+ mapper.mapInstanceToEntity(instance, entity);
+
+ Assertions.assertThat(entity.getInputs())
+ .hasSize(8);
+ verify(repository, never())
+ .remove(any());
+
+ TestUtils.assertUserTaskEntityInputs(entity, instance);
+
+ DefaultUserTaskInstance instance2 = new DefaultUserTaskInstance();
+
+ mapper.mapEntityToInstance(entity, instance2);
+
+ Assertions.assertThat(instance2.getInputs())
+ .hasSize(8);
+
+ TestUtils.assertUserTaskInstanceInputs(instance2, entity);
+
+ Assertions.assertThat(instance2.getInputs())
+ .usingRecursiveComparison()
+ .isEqualTo(instance.getInputs());
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/TaskMetadataEntityMapperTest.java b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/TaskMetadataEntityMapperTest.java
new file mode 100644
index 00000000000..1e6d18de1fd
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/TaskMetadataEntityMapperTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.usertask.jpa.mapper;
+
+import org.assertj.core.api.Assertions;
+import org.jbpm.usertask.jpa.mapper.utils.TestUtils;
+import org.jbpm.usertask.jpa.model.TaskMetadataEntity;
+import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
+import org.jbpm.usertask.jpa.repository.TaskMetadataRepository;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class TaskMetadataEntityMapperTest {
+
+ @Mock
+ private TaskMetadataRepository repository;
+ private TaskMetadataEntityMapper mapper;
+
+ @BeforeEach
+ public void setup() {
+ mapper = new TaskMetadataEntityMapper(repository);
+ }
+
+ @Test
+ public void testMapMetadataFromInstanceToEntity() {
+ UserTaskInstance instance = TestUtils.createUserTaskInstance();
+ UserTaskInstanceEntity entity = new UserTaskInstanceEntity();
+
+ mapper.mapInstanceToEntity(instance, entity);
+
+ Assertions.assertThat(entity.getMetadata())
+ .hasSize(6);
+ verify(repository, never())
+ .remove(any());
+ TestUtils.assertUserTaskEntityMetadata(entity, instance);
+
+ instance.getMetadata().remove("ProcessId");
+ instance.getMetadata().remove("ProcessType");
+
+ mapper.mapInstanceToEntity(instance, entity);
+
+ Assertions.assertThat(entity.getMetadata())
+ .hasSize(4);
+ verify(repository, times(2))
+ .remove(any());
+
+ TestUtils.assertUserTaskEntityMetadata(entity, instance);
+
+ instance.getMetadata().clear();
+
+ mapper.mapInstanceToEntity(instance, entity);
+
+ Assertions.assertThat(entity.getMetadata())
+ .hasSize(0);
+ verify(repository, times(6))
+ .remove(any());
+ }
+
+ @Test
+ public void testMapMetadataFromEntityToInstance() {
+
+ UserTaskInstance instance = TestUtils.createUserTaskInstance();
+ UserTaskInstanceEntity entity = new UserTaskInstanceEntity();
+
+ TaskMetadataEntity metadata = new TaskMetadataEntity();
+ metadata.setName("ProcessId");
+ metadata.setValue("1234");
+
+ TaskMetadataEntity metadata2 = new TaskMetadataEntity();
+ metadata2.setName("CustomMetadata");
+ metadata2.setValue("This is the metadata value");
+
+ entity.addMetadata(metadata);
+ entity.addMetadata(metadata2);
+
+ mapper.mapEntityToInstance(entity, instance);
+
+ Assertions.assertThat(instance.getMetadata())
+ .hasSize(2);
+
+ TestUtils.assertUserTaskInstanceMetadata(instance, entity);
+
+ entity.getMetadata().clear();
+
+ mapper.mapEntityToInstance(entity, instance);
+
+ Assertions.assertThat(instance.getMetadata())
+ .hasSize(0);
+ }
+
+ @Test
+ public void testFullMappingCircle() {
+
+ UserTaskInstance instance = TestUtils.createUserTaskInstance();
+ UserTaskInstanceEntity entity = new UserTaskInstanceEntity();
+
+ mapper.mapInstanceToEntity(instance, entity);
+
+ Assertions.assertThat(entity.getMetadata())
+ .hasSize(6);
+ verify(repository, never())
+ .remove(any());
+
+ TestUtils.assertUserTaskEntityMetadata(entity, instance);
+
+ DefaultUserTaskInstance instance2 = new DefaultUserTaskInstance();
+
+ mapper.mapEntityToInstance(entity, instance2);
+
+ Assertions.assertThat(instance2.getMetadata())
+ .hasSize(6);
+
+ TestUtils.assertUserTaskInstanceMetadata(instance2, entity);
+
+ Assertions.assertThat(instance2.getMetadata())
+ .usingRecursiveComparison()
+ .isEqualTo(instance.getMetadata());
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/TaskOutputsEntityMapperTest.java b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/TaskOutputsEntityMapperTest.java
new file mode 100644
index 00000000000..52da167c9d1
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/TaskOutputsEntityMapperTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.usertask.jpa.mapper;
+
+import java.nio.charset.StandardCharsets;
+
+import org.assertj.core.api.Assertions;
+import org.jbpm.usertask.jpa.mapper.utils.TestUtils;
+import org.jbpm.usertask.jpa.model.TaskOutputEntity;
+import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
+import org.jbpm.usertask.jpa.repository.TaskOutputRepository;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class TaskOutputsEntityMapperTest {
+
+ @Mock
+ private TaskOutputRepository repository;
+ private TaskOutputsEntityMapper mapper;
+
+ @BeforeEach
+ public void setup() {
+ mapper = new TaskOutputsEntityMapper(repository);
+ }
+
+ @Test
+ public void testMapOutputsFromInstanceToEntity() {
+ UserTaskInstance instance = TestUtils.createUserTaskInstance();
+ UserTaskInstanceEntity entity = new UserTaskInstanceEntity();
+
+ mapper.mapInstanceToEntity(instance, entity);
+
+ Assertions.assertThat(entity.getOutputs())
+ .hasSize(8);
+ verify(repository, never())
+ .remove(any());
+ TestUtils.assertUserTaskEntityOutputs(entity, instance);
+
+ instance.getOutputs().remove("out_string");
+ instance.getOutputs().remove("out_integer");
+ instance.getOutputs().remove("out_null");
+
+ mapper.mapInstanceToEntity(instance, entity);
+
+ Assertions.assertThat(entity.getOutputs())
+ .hasSize(5);
+ verify(repository, times(3))
+ .remove(any());
+
+ TestUtils.assertUserTaskEntityOutputs(entity, instance);
+
+ instance.getOutputs().clear();
+
+ mapper.mapInstanceToEntity(instance, entity);
+
+ Assertions.assertThat(entity.getOutputs())
+ .hasSize(0);
+ verify(repository, times(8))
+ .remove(any());
+ }
+
+ @Test
+ public void testMapOutputsFromEntityToInstance() {
+ final String stringValue = "This is the output value";
+
+ UserTaskInstance instance = TestUtils.createUserTaskInstance();
+ UserTaskInstanceEntity entity = new UserTaskInstanceEntity();
+
+ TaskOutputEntity output = new TaskOutputEntity();
+ output.setName("out_string");
+ output.setValue(stringValue.getBytes(StandardCharsets.UTF_8));
+
+ entity.addOutput(output);
+
+ mapper.mapEntityToInstance(entity, instance);
+
+ Assertions.assertThat(instance.getOutputs())
+ .hasSize(1);
+
+ TestUtils.assertUserTaskInstanceOutputs(instance, entity);
+
+ entity.getOutputs().clear();
+
+ mapper.mapEntityToInstance(entity, instance);
+
+ Assertions.assertThat(instance.getOutputs())
+ .hasSize(0);
+ }
+
+ @Test
+ public void testMappingRoundCircle() {
+
+ UserTaskInstance instance = TestUtils.createUserTaskInstance();
+ UserTaskInstanceEntity entity = new UserTaskInstanceEntity();
+
+ mapper.mapInstanceToEntity(instance, entity);
+
+ Assertions.assertThat(entity.getOutputs())
+ .hasSize(8);
+ verify(repository, never())
+ .remove(any());
+ TestUtils.assertUserTaskEntityOutputs(entity, instance);
+
+ DefaultUserTaskInstance instance2 = new DefaultUserTaskInstance();
+
+ mapper.mapEntityToInstance(entity, instance2);
+
+ Assertions.assertThat(instance2.getOutputs())
+ .hasSize(8);
+
+ TestUtils.assertUserTaskInstanceOutputs(instance2, entity);
+
+ Assertions.assertThat(instance2.getOutputs())
+ .usingRecursiveComparison()
+ .isEqualTo(instance.getOutputs());
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/UserTaskInstanceEntityMapperTest.java b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/UserTaskInstanceEntityMapperTest.java
new file mode 100644
index 00000000000..3b49d9b445d
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/UserTaskInstanceEntityMapperTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.usertask.jpa.mapper;
+
+import org.jbpm.usertask.jpa.mapper.utils.TestUtils;
+import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.mockito.Mockito.*;
+
+@ExtendWith(MockitoExtension.class)
+public class UserTaskInstanceEntityMapperTest {
+
+ @Mock
+ private AttachmentsEntityMapper attachmentsEntityMapper;
+ @Mock
+ private CommentsEntityMapper commentsEntityMapper;
+ @Mock
+ private TaskMetadataEntityMapper metadataEntityMapper;
+ @Mock
+ private TaskInputsEntityMapper inputsEntityMapper;
+ @Mock
+ private TaskOutputsEntityMapper outputsEntityMapper;
+
+ private UserTaskInstanceEntityMapper userTaskInstanceEntityMapper;
+
+ @BeforeEach
+ public void setUp() {
+ this.userTaskInstanceEntityMapper = new UserTaskInstanceEntityMapper(attachmentsEntityMapper, commentsEntityMapper, metadataEntityMapper, inputsEntityMapper, outputsEntityMapper);
+ }
+
+ @Test
+ public void testUserTaskInstanceToUserTaskEntityMapper() {
+ UserTaskInstance userTaskInstance = TestUtils.createUserTaskInstance();
+
+ UserTaskInstanceEntity userTaskInstanceEntity = new UserTaskInstanceEntity();
+
+ userTaskInstanceEntityMapper.mapTaskInstanceToEntity(userTaskInstance, userTaskInstanceEntity);
+
+ verify(attachmentsEntityMapper, times(1))
+ .mapInstanceToEntity(same(userTaskInstance), same(userTaskInstanceEntity));
+ verify(commentsEntityMapper, times(1))
+ .mapInstanceToEntity(same(userTaskInstance), same(userTaskInstanceEntity));
+ verify(metadataEntityMapper, times(1))
+ .mapInstanceToEntity(same(userTaskInstance), same(userTaskInstanceEntity));
+ verify(inputsEntityMapper, times(1))
+ .mapInstanceToEntity(same(userTaskInstance), same(userTaskInstanceEntity));
+ verify(outputsEntityMapper, times(1))
+ .mapInstanceToEntity(same(userTaskInstance), same(userTaskInstanceEntity));
+
+ TestUtils.assertUserTaskEntityData(userTaskInstanceEntity, userTaskInstance);
+ TestUtils.assertUserTaskEntityPotentialUserAndGroups(userTaskInstanceEntity, userTaskInstance);
+ TestUtils.assertUserTaskEntityAdminUserAndGroups(userTaskInstanceEntity, userTaskInstance);
+ TestUtils.assertUserTaskEntityExcludedUsers(userTaskInstanceEntity, userTaskInstance);
+ }
+
+ @Test
+ public void testUserTaskEntityToUserTaskInstanceMapper() {
+ UserTaskInstanceEntity userTaskInstanceEntity = TestUtils.createUserTaskInstanceEntity();
+
+ UserTaskInstance userTaskInstance = userTaskInstanceEntityMapper.mapTaskEntityToInstance(userTaskInstanceEntity);
+
+ verify(attachmentsEntityMapper, times(1))
+ .mapEntityToInstance(same(userTaskInstanceEntity), same(userTaskInstance));
+ verify(commentsEntityMapper, times(1))
+ .mapEntityToInstance(same(userTaskInstanceEntity), same(userTaskInstance));
+ verify(metadataEntityMapper, times(1))
+ .mapEntityToInstance(same(userTaskInstanceEntity), same(userTaskInstance));
+ verify(inputsEntityMapper, times(1))
+ .mapEntityToInstance(same(userTaskInstanceEntity), same(userTaskInstance));
+ verify(outputsEntityMapper, times(1))
+ .mapEntityToInstance(same(userTaskInstanceEntity), same(userTaskInstance));
+
+ TestUtils.assertUserTaskInstanceData(userTaskInstance, userTaskInstanceEntity);
+ TestUtils.assertUserTaskInstancePotentialUserAndGroups(userTaskInstance, userTaskInstanceEntity);
+ TestUtils.assertUserTaskInstanceAdminUserAndGroups(userTaskInstance, userTaskInstanceEntity);
+ TestUtils.assertUserTaskInstanceExcludedUsers(userTaskInstance, userTaskInstanceEntity);
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/models/Person.java b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/models/Person.java
new file mode 100644
index 00000000000..3c1d911f1e6
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/models/Person.java
@@ -0,0 +1,76 @@
+/*
+ * 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.usertask.jpa.mapper.models;
+
+import java.util.Objects;
+
+public class Person {
+ String name;
+ String lastName;
+ int age;
+
+ public Person() {
+ }
+
+ public Person(String name, String lastName, int age) {
+ this.name = name;
+ this.lastName = lastName;
+ this.age = age;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ Person person = (Person) o;
+ return age == person.age && Objects.equals(name, person.name) && Objects.equals(lastName, person.lastName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, lastName, age);
+ }
+}
diff --git a/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/utils/TestUtils.java b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/utils/TestUtils.java
new file mode 100644
index 00000000000..b1b4373f369
--- /dev/null
+++ b/addons/common/jbpm-usertask-storage-jpa/src/test/java/org/jbpm/usertask/jpa/mapper/utils/TestUtils.java
@@ -0,0 +1,317 @@
+/*
+ * 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.usertask.jpa.mapper.utils;
+
+import java.util.*;
+
+import org.assertj.core.api.Assertions;
+import org.jbpm.usertask.jpa.mapper.models.Person;
+import org.jbpm.usertask.jpa.model.AttachmentEntity;
+import org.jbpm.usertask.jpa.model.CommentEntity;
+import org.jbpm.usertask.jpa.model.TaskDataEntity;
+import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
+import org.kie.kogito.usertask.lifecycle.UserTaskState;
+import org.kie.kogito.usertask.model.Attachment;
+import org.kie.kogito.usertask.model.Comment;
+
+public class TestUtils {
+
+ private TestUtils() {
+ }
+
+ public static void assertUserTaskEntityData(UserTaskInstanceEntity userTaskInstanceEntity, UserTaskInstance userTaskInstance) {
+ Assertions.assertThat(userTaskInstanceEntity)
+ .hasFieldOrPropertyWithValue("id", userTaskInstance.getId())
+ .hasFieldOrPropertyWithValue("userTaskId", userTaskInstance.getUserTaskId())
+ .hasFieldOrPropertyWithValue("taskName", userTaskInstance.getTaskName())
+ .hasFieldOrPropertyWithValue("taskDescription", userTaskInstance.getTaskDescription())
+ .hasFieldOrPropertyWithValue("taskPriority", userTaskInstance.getTaskPriority())
+ .hasFieldOrPropertyWithValue("status", userTaskInstance.getStatus().getName())
+ .hasFieldOrPropertyWithValue("terminationType", userTaskInstance.getStatus().getTerminate().toString())
+ .hasFieldOrPropertyWithValue("externalReferenceId", userTaskInstance.getExternalReferenceId())
+ .hasFieldOrPropertyWithValue("actualOwner", userTaskInstance.getActualOwner());
+ }
+
+ public static void assertUserTaskInstanceData(UserTaskInstance userTaskInstance, UserTaskInstanceEntity userTaskInstanceEntity) {
+ UserTaskState.TerminationType terminationType =
+ Objects.isNull(userTaskInstanceEntity.getTerminationType()) ? null : UserTaskState.TerminationType.valueOf(userTaskInstanceEntity.getTerminationType());
+ UserTaskState state = UserTaskState.of(userTaskInstanceEntity.getStatus(), terminationType);
+
+ Assertions.assertThat(userTaskInstance)
+ .hasFieldOrPropertyWithValue("id", userTaskInstanceEntity.getId())
+ .hasFieldOrPropertyWithValue("userTaskId", userTaskInstanceEntity.getUserTaskId())
+ .hasFieldOrPropertyWithValue("taskName", userTaskInstanceEntity.getTaskName())
+ .hasFieldOrPropertyWithValue("taskDescription", userTaskInstanceEntity.getTaskDescription())
+ .hasFieldOrPropertyWithValue("taskPriority", userTaskInstanceEntity.getTaskPriority())
+ .hasFieldOrPropertyWithValue("status", state)
+ .hasFieldOrPropertyWithValue("externalReferenceId", userTaskInstanceEntity.getExternalReferenceId())
+ .hasFieldOrPropertyWithValue("actualOwner", userTaskInstanceEntity.getActualOwner());
+ }
+
+ public static void assertUserTaskEntityPotentialUserAndGroups(UserTaskInstanceEntity userTaskEntity, UserTaskInstance userTaskInstance) {
+ assertUserOrGroupsAssignments(userTaskEntity.getPotentialUsers(), userTaskInstance.getPotentialUsers());
+ assertUserOrGroupsAssignments(userTaskEntity.getPotentialGroups(), userTaskInstance.getPotentialGroups());
+ }
+
+ public static void assertUserTaskEntityAdminUserAndGroups(UserTaskInstanceEntity userTaskEntity, UserTaskInstance userTaskInstance) {
+ assertUserOrGroupsAssignments(userTaskEntity.getAdminUsers(), userTaskInstance.getAdminUsers());
+ assertUserOrGroupsAssignments(userTaskEntity.getAdminGroups(), userTaskInstance.getAdminGroups());
+ }
+
+ public static void assertUserTaskEntityExcludedUsers(UserTaskInstanceEntity userTaskEntity, UserTaskInstance userTaskInstance) {
+ assertUserOrGroupsAssignments(userTaskEntity.getExcludedUsers(), userTaskInstance.getExcludedUsers());
+ }
+
+ public static void assertUserTaskInstancePotentialUserAndGroups(UserTaskInstance userTaskInstance, UserTaskInstanceEntity userTaskEntity) {
+ assertUserOrGroupsAssignments(userTaskInstance.getPotentialUsers(), userTaskEntity.getPotentialUsers());
+ assertUserOrGroupsAssignments(userTaskInstance.getPotentialGroups(), userTaskEntity.getPotentialGroups());
+ }
+
+ public static void assertUserTaskInstanceAdminUserAndGroups(UserTaskInstance userTaskInstance, UserTaskInstanceEntity userTaskEntity) {
+ assertUserOrGroupsAssignments(userTaskInstance.getAdminUsers(), userTaskEntity.getAdminUsers());
+ assertUserOrGroupsAssignments(userTaskInstance.getAdminGroups(), userTaskEntity.getAdminGroups());
+ }
+
+ public static void assertUserTaskInstanceExcludedUsers(UserTaskInstance userTaskInstance, UserTaskInstanceEntity userTaskEntity) {
+ assertUserOrGroupsAssignments(userTaskInstance.getExcludedUsers(), userTaskEntity.getExcludedUsers());
+ }
+
+ private static void assertUserOrGroupsAssignments(Collection entityAssignments, Collection instanceAssignments) {
+ Assertions.assertThat(entityAssignments)
+ .hasSize(instanceAssignments.size())
+ .containsExactlyInAnyOrder(instanceAssignments.toArray(new String[0]));
+ }
+
+ public static void assertUserTaskEntityComments(Collection entityComments, Collection instanceComments) {
+ Assertions.assertThat(entityComments)
+ .hasSize(instanceComments.size());
+
+ entityComments.forEach(entityComment -> {
+ Optional optional = instanceComments.stream()
+ .filter(instanceComment -> instanceComment.getId().equals(entityComment.getId()))
+ .findFirst();
+
+ Assertions.assertThat(optional)
+ .isPresent();
+
+ Comment instanceComment = optional.get();
+
+ Assertions.assertThat(entityComment)
+ .hasFieldOrPropertyWithValue("id", instanceComment.getId())
+ .hasFieldOrPropertyWithValue("comment", instanceComment.getContent())
+ .hasFieldOrPropertyWithValue("updatedBy", instanceComment.getUpdatedBy())
+ .matches(entity -> entity.getUpdatedAt().getTime() == instanceComment.getUpdatedAt().getTime());
+ });
+ }
+
+ public static void assertUserTaskInstanceComments(Collection instanceComments, Collection entityComments) {
+ Assertions.assertThat(instanceComments)
+ .hasSize(instanceComments.size());
+
+ instanceComments.forEach(instanceComment -> {
+ Optional optional = entityComments.stream()
+ .filter(entityComment -> entityComment.getId().equals(instanceComment.getId()))
+ .findFirst();
+
+ Assertions.assertThat(optional)
+ .isPresent();
+
+ CommentEntity entityComment = optional.get();
+
+ Assertions.assertThat(instanceComment)
+ .hasFieldOrPropertyWithValue("id", entityComment.getId())
+ .hasFieldOrPropertyWithValue("content", entityComment.getComment())
+ .hasFieldOrPropertyWithValue("updatedBy", entityComment.getUpdatedBy())
+ .matches(entity -> entity.getUpdatedAt().getTime() == entityComment.getUpdatedAt().getTime());
+ });
+ }
+
+ public static void assertUserTaskEntityAttachments(Collection entityAttachments, Collection instanceAttachments) {
+ Assertions.assertThat(entityAttachments)
+ .hasSize(instanceAttachments.size());
+
+ entityAttachments.forEach(entityAttachment -> {
+ Optional optional = instanceAttachments.stream()
+ .filter(instanceAttachment -> instanceAttachment.getId().equals(entityAttachment.getId()))
+ .findFirst();
+
+ Assertions.assertThat(optional)
+ .isPresent();
+
+ Attachment instanceAttachment = optional.get();
+
+ Assertions.assertThat(entityAttachment)
+ .hasFieldOrPropertyWithValue("id", instanceAttachment.getId())
+ .hasFieldOrPropertyWithValue("name", instanceAttachment.getName())
+ .hasFieldOrPropertyWithValue("updatedBy", instanceAttachment.getUpdatedBy())
+ .matches(entity -> entity.getUpdatedAt().getTime() == instanceAttachment.getUpdatedAt().getTime())
+ .hasFieldOrPropertyWithValue("url", instanceAttachment.getContent().toString());
+ });
+ }
+
+ public static void assertUserTaskInstanceAttachments(Collection instanceAttachments, Collection entityAttachments) {
+ Assertions.assertThat(instanceAttachments)
+ .hasSize(instanceAttachments.size());
+
+ instanceAttachments.forEach(instanceAttachment -> {
+ Optional optional = entityAttachments.stream()
+ .filter(entityAttachment -> entityAttachment.getId().equals(instanceAttachment.getId()))
+ .findFirst();
+
+ Assertions.assertThat(optional)
+ .isPresent();
+
+ AttachmentEntity entityAttachment = optional.get();
+
+ Assertions.assertThat(instanceAttachment)
+ .hasFieldOrPropertyWithValue("id", entityAttachment.getId())
+ .hasFieldOrPropertyWithValue("name", entityAttachment.getName())
+ .hasFieldOrPropertyWithValue("updatedBy", entityAttachment.getUpdatedBy())
+ .matches(entity -> entity.getUpdatedAt().getTime() == entityAttachment.getUpdatedAt().getTime())
+ .matches(entity -> entity.getContent().toString().equals(entityAttachment.getUrl()));
+ });
+ }
+
+ public static void assertUserTaskEntityInputs(UserTaskInstanceEntity userTaskInstanceEntity, UserTaskInstance userTaskInstance) {
+ assertUserTaskEntityMapData(userTaskInstanceEntity.getInputs(), userTaskInstance.getInputs());
+ }
+
+ public static void assertUserTaskEntityOutputs(UserTaskInstanceEntity userTaskInstanceEntity, UserTaskInstance userTaskInstance) {
+ assertUserTaskEntityMapData(userTaskInstanceEntity.getOutputs(), userTaskInstance.getOutputs());
+ }
+
+ public static void assertUserTaskEntityMetadata(UserTaskInstanceEntity userTaskInstanceEntity, UserTaskInstance userTaskInstance) {
+ assertUserTaskEntityMapData(userTaskInstanceEntity.getMetadata(), userTaskInstance.getMetadata());
+ }
+
+ private static void assertUserTaskEntityMapData(Collection extends TaskDataEntity> entityData, Map instanceData) {
+ Assertions.assertThat(entityData.size())
+ .isEqualTo(instanceData.size());
+
+ entityData.stream().forEach(entity -> {
+ Object value = instanceData.get(entity.getName());
+ Assertions.assertThat(entity)
+ .isNotNull()
+ .matches(e -> instanceData.containsKey(e.getName()))
+ .matches(e -> Objects.isNull(e.getValue()) ? Objects.isNull(value) : e.getJavaType().equals(value.getClass().getName()));
+ });
+ }
+
+ public static void assertUserTaskInstanceInputs(UserTaskInstance userTaskInstance, UserTaskInstanceEntity userTaskInstanceEntity) {
+ assertUserTaskInstanceMapData(userTaskInstance.getInputs(), userTaskInstanceEntity.getInputs());
+ }
+
+ public static void assertUserTaskInstanceOutputs(UserTaskInstance userTaskInstance, UserTaskInstanceEntity userTaskInstanceEntity) {
+ assertUserTaskInstanceMapData(userTaskInstance.getOutputs(), userTaskInstanceEntity.getOutputs());
+ }
+
+ public static void assertUserTaskInstanceMetadata(UserTaskInstance userTaskInstance, UserTaskInstanceEntity userTaskInstanceEntity) {
+ assertUserTaskInstanceMapData(userTaskInstance.getMetadata(), userTaskInstanceEntity.getMetadata());
+ }
+
+ private static void assertUserTaskInstanceMapData(Map instanceData, Collection extends TaskDataEntity> entityData) {
+ Assertions.assertThat(instanceData.size())
+ .isEqualTo(entityData.size());
+
+ instanceData.forEach((key, value) -> {
+ Optional extends TaskDataEntity> optional = entityData.stream().filter(data -> data.getName().equals(key)).findFirst();
+
+ Assertions.assertThat(optional)
+ .isPresent();
+
+ if (Objects.nonNull(value)) {
+ TaskDataEntity data = optional.get();
+ Assertions.assertThat(value.getClass().getName())
+ .isEqualTo(data.getJavaType());
+ }
+ });
+ }
+
+ public static DefaultUserTaskInstance createUserTaskInstance() {
+ DefaultUserTaskInstance instance = new DefaultUserTaskInstance();
+ instance.setId(UUID.randomUUID().toString());
+ instance.setUserTaskId("user-task-id");
+ instance.setTaskName("test-task");
+ instance.setTaskDescription("this is a test task description");
+ instance.setTaskPriority("1");
+ instance.setStatus(UserTaskState.of("Complete", UserTaskState.TerminationType.COMPLETED));
+
+ instance.setActualOwner("Homer");
+ instance.setPotentialUsers(Set.of("Bart", "Liza"));
+ instance.setPotentialGroups(Set.of("Simpson", "Family"));
+ instance.setAdminUsers(Set.of("Seymour"));
+ instance.setAdminGroups(Set.of("Administrators", "Managers"));
+ instance.setExcludedUsers(Set.of("Ned", "Bart"));
+
+ instance.setExternalReferenceId("external-reference-id");
+
+ instance.setMetadata("ProcessId", "process-id");
+ instance.setMetadata("ProcessType", "BPMN");
+ instance.setMetadata("ProcessVersion", "1.0.0");
+ instance.setMetadata("boolean", true);
+ instance.setMetadata("integer", 0);
+ instance.setMetadata("null", 0);
+
+ instance.setInput("in_string", "hello this is a string");
+ instance.setInput("in_integer", 1);
+ instance.setInput("in_long", 1000L);
+ instance.setInput("in_float", 1.02f);
+ instance.setInput("in_boolean", true);
+ instance.setInput("in_date", new Date());
+ instance.setInput("in_person", new Person("Ned", "Stark", 50));
+ instance.setInput("in_null", null);
+
+ instance.setOutput("out_string", "hello this is an output string");
+ instance.setOutput("out_integer", 12);
+ instance.setOutput("out_long", 2000L);
+ instance.setOutput("out_float", 3.5f);
+ instance.setOutput("out_boolean", false);
+ instance.setOutput("out_date", new Date());
+ instance.setOutput("out_person", new Person("Jon", "Snow", 17));
+ instance.setOutput("out_null", null);
+
+ return instance;
+ }
+
+ public static UserTaskInstanceEntity createUserTaskInstanceEntity() {
+ UserTaskInstanceEntity instance = new UserTaskInstanceEntity();
+
+ instance.setId(UUID.randomUUID().toString());
+ instance.setUserTaskId("user-task-id");
+ instance.setTaskName("test-task");
+ instance.setTaskDescription("this is a test task description");
+ instance.setTaskPriority("1");
+ instance.setStatus("Complete");
+ instance.setTerminationType(UserTaskState.TerminationType.COMPLETED.name());
+
+ instance.setActualOwner("Homer");
+ instance.setPotentialUsers(Set.of("Bart"));
+ instance.setPotentialGroups(Set.of("Simpson", "Family"));
+ instance.setAdminUsers(Set.of("Seymour"));
+ instance.setAdminGroups(Set.of("Administrators", "Managers"));
+ instance.setExcludedUsers(Set.of("Ned"));
+
+ instance.setExternalReferenceId("external-reference-id");
+
+ return instance;
+ }
+}
diff --git a/addons/common/pom.xml b/addons/common/pom.xml
index 1a12e57c0c9..fe7ccb1dcf6 100644
--- a/addons/common/pom.xml
+++ b/addons/common/pom.xml
@@ -55,6 +55,7 @@
task-management
marshallers
flyway
+ jbpm-usertask-storage-jpa
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/auth/IdentityProviders.java b/api/kogito-api/src/main/java/org/kie/kogito/auth/IdentityProviders.java
index 2d1073893f7..8d560bc801f 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/auth/IdentityProviders.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/auth/IdentityProviders.java
@@ -20,6 +20,7 @@
import java.util.Collection;
import java.util.Collections;
+import java.util.List;
public class IdentityProviders {
@@ -52,8 +53,8 @@ public boolean hasRole(String role) {
}
- public static IdentityProvider of(String name) {
- return new DefaultIdentityProvider(name, Collections.emptyList());
+ public static IdentityProvider of(String name, String... roles) {
+ return new DefaultIdentityProvider(name, List.of(roles));
}
public static IdentityProvider of(String name, Collection roles) {
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 9e101eb9d4b..e28e23befed 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
@@ -39,7 +39,7 @@ public interface UserTaskInstance {
boolean hasActualOwner();
- void setActuaOwner(String actualOwner);
+ void setActualOwner(String actualOwner);
String getActualOwner();
diff --git a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstances.java b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstances.java
index 7f294b15fed..8372c4e2747 100644
--- a/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstances.java
+++ b/api/kogito-api/src/main/java/org/kie/kogito/usertask/UserTaskInstances.java
@@ -40,6 +40,6 @@ public interface UserTaskInstances {
UserTaskInstance update(UserTaskInstance userTaskInstance);
- UserTaskInstance remove(String userTaskInstanceId);
+ UserTaskInstance remove(UserTaskInstance userTaskInstanceId);
}
diff --git a/jbpm/jbpm-usertask-workitem/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
index 71d34053787..cf30b7ed2bf 100644
--- a/jbpm/jbpm-usertask-workitem/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
@@ -73,10 +73,14 @@ public Optional activateWorkItemHandler(KogitoWorkItemManage
UserTask userTask = userTasks.userTaskById((String) workItem.getParameter(KogitoWorkItem.PARAMETER_UNIQUE_TASK_ID));
DefaultUserTaskInstance instance = (DefaultUserTaskInstance) userTask.createInstance();
+
+ instance.setExternalReferenceId(workItem.getStringId());
+
+ userTask.instances().create(instance);
+
instance.setTaskName((String) workItem.getParameter(TASK_NAME));
instance.setTaskDescription((String) workItem.getParameter(DESCRIPTION));
instance.setTaskPriority(priority != null ? priority.toString() : null);
- instance.setExternalReferenceId(workItem.getStringId());
instance.setMetadata("ProcessId", workItem.getProcessInstance().getProcessId());
instance.setMetadata("ProcessType", workItem.getProcessInstance().getProcess().getType());
@@ -87,7 +91,6 @@ public Optional activateWorkItemHandler(KogitoWorkItemManage
instance.setMetadata("RootProcessInstanceId", workItem.getProcessInstance().getRootProcessInstanceId());
instance.setMetadata("ParentProcessInstanceId", workItem.getProcessInstance().getParentProcessInstanceId());
- userTask.instances().create(instance);
instance.fireInitialStateChange();
workItem.getParameters().entrySet().stream().filter(e -> !HumanTaskNode.TASK_PARAMETERS.contains(e.getKey())).forEach(e -> instance.setInput(e.getKey(), e.getValue()));
@@ -109,6 +112,9 @@ public Optional activateWorkItemHandler(KogitoWorkItemManage
@Override
public Optional completeWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workItem, WorkItemTransition transition) {
+ if (transition.data().containsKey("Notify")) {
+ return Optional.empty();
+ }
UserTasks userTasks = handler.getApplication().get(UserTasks.class);
UserTask userTask = userTasks.userTaskById((String) workItem.getParameter(KogitoWorkItem.PARAMETER_UNIQUE_TASK_ID));
userTask.instances().findById(workItem.getExternalReferenceId()).ifPresent(ut -> {
@@ -122,6 +128,9 @@ public Optional completeWorkItemHandler(KogitoWorkItemManage
@Override
public Optional abortWorkItemHandler(KogitoWorkItemManager manager, KogitoWorkItemHandler handler, KogitoWorkItem workItem, WorkItemTransition transition) {
+ if (transition.data().containsKey("Notify")) {
+ return Optional.empty();
+ }
UserTasks userTasks = handler.getApplication().get(UserTasks.class);
UserTask userTask = userTasks.userTaskById((String) workItem.getParameter(KogitoWorkItem.PARAMETER_UNIQUE_TASK_ID));
userTask.instances().findById(workItem.getExternalReferenceId()).ifPresent(ut -> {
diff --git a/jbpm/jbpm-usertask-workitem/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
index 7d0bc70557a..116eb8716fd 100644
--- a/jbpm/jbpm-usertask-workitem/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
@@ -59,6 +59,7 @@ public void onUserTaskState(UserTaskStateEvent event) {
processes.processById(processId).instances().findById(processInstanceId).ifPresent(pi -> {
Map data = new HashMap<>(event.getUserTaskInstance().getOutputs());
data.put("ActorId", event.getUserTaskInstance().getActualOwner());
+ data.put("Notify", false);
pi.completeWorkItem(event.getUserTaskInstance().getExternalReferenceId(), data);
});
diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/AbstractUserTask.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/AbstractUserTask.java
index 68f85aceb3f..795c0f25e9c 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
@@ -74,8 +74,9 @@ public UserTaskInstance createInstance() {
instance.setPotentialUsers(getPotentialUsers());
instance.setPotentialGroups(getPotentialGroups());
instance.setAdminUsers(getAdminUsers());
- instance.setPotentialGroups(getPotentialGroups());
+ instance.setAdminGroups(getAdminGroups());
instance.setExcludedUsers(getExcludedUsers());
+ instance.setInstances(this.instances());
return instance;
}
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 fd6d3ce21c3..266e674887e 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
@@ -18,15 +18,7 @@
*/
package org.kie.kogito.usertask.impl;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.UUID;
+import java.util.*;
import org.kie.kogito.auth.IdentityProvider;
import org.kie.kogito.internal.usertask.event.KogitoUserTaskEventSupport;
@@ -74,7 +66,7 @@ public class DefaultUserTaskInstance implements UserTaskInstance {
@JsonIgnore
private KogitoUserTaskEventSupport userTaskEventSupport;
@JsonIgnore
- private UserTaskLifeCycle setUserTaskLifeCycle;
+ private UserTaskLifeCycle userTaskLifeCycle;
public DefaultUserTaskInstance() {
this.inputs = new HashMap<>();
@@ -94,7 +86,6 @@ public DefaultUserTaskInstance(UserTask userTask) {
this();
this.id = UUID.randomUUID().toString();
this.userTask = userTask;
- this.instances = userTask.instances();
}
public void setUserTaskEventSupport(KogitoUserTaskEventSupport userTaskEventSupport) {
@@ -102,7 +93,7 @@ public void setUserTaskEventSupport(KogitoUserTaskEventSupport userTaskEventSupp
}
public void setUserTaskLifeCycle(UserTaskLifeCycle userTaskLifeCycle) {
- this.setUserTaskLifeCycle = userTaskLifeCycle;
+ this.userTaskLifeCycle = userTaskLifeCycle;
}
public void setInstances(UserTaskInstances instances) {
@@ -142,7 +133,7 @@ public boolean hasActualOwner() {
}
@Override
- public void setActuaOwner(String actualOwner) {
+ public void setActualOwner(String actualOwner) {
this.actualOwner = actualOwner;
if (this.userTaskEventSupport != null) {
this.userTaskEventSupport.fireOneUserTaskStateChange(this, this.status, this.status);
@@ -166,15 +157,14 @@ public void setExternalReferenceId(String externalReferenceId) {
@Override
public void transition(String transitionId, Map data, IdentityProvider identity) {
- Optional next = Optional.of(this.setUserTaskLifeCycle.newTransitionToken(transitionId, this, data));
+ Optional next = Optional.of(this.userTaskLifeCycle.newTransitionToken(transitionId, this, data));
while (next.isPresent()) {
UserTaskTransitionToken transition = next.get();
- next = this.setUserTaskLifeCycle.transition(this, transition, identity);
+ next = this.userTaskLifeCycle.transition(this, transition, identity);
this.status = transition.target();
- this.updatePersistenceOrRemove();
this.userTaskEventSupport.fireOneUserTaskStateChange(this, transition.source(), transition.target());
}
-
+ this.updatePersistenceOrRemove();
}
private void updatePersistence() {
@@ -185,7 +175,7 @@ private void updatePersistence() {
private void updatePersistenceOrRemove() {
if (this.status.isTerminate()) {
- this.instances.remove(this.id);
+ this.instances.remove(this);
} else {
this.instances.update(this);
}
@@ -205,7 +195,7 @@ public Map getInputs() {
}
public void setInputs(Map inputs) {
- inputs.forEach(this::setInput);
+ this.inputs = inputs;
}
public Map getOutputs() {
@@ -213,7 +203,7 @@ public Map getOutputs() {
}
public void setOutputs(Map outputs) {
- outputs.forEach(this::setOutput);
+ this.outputs = outputs;
}
@Override
@@ -451,8 +441,6 @@ public Attachment removeAttachment(Attachment attachment) {
public void setAttachments(List attachments) {
this.attachments = attachments;
- updatePersistence();
-
}
/**
@@ -509,7 +497,6 @@ public Comment removeComment(Comment comment) {
public void setComments(List comments) {
this.comments = comments;
- updatePersistence();
}
public void setMetadata(String key, Object value) {
@@ -523,7 +510,6 @@ public Map getMetadata() {
public void setMetadata(Map metadata) {
this.metadata = metadata;
- updatePersistence();
}
@Override
diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/InMemoryUserTaskInstances.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/InMemoryUserTaskInstances.java
index def2088da39..256144c4849 100644
--- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/InMemoryUserTaskInstances.java
+++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/InMemoryUserTaskInstances.java
@@ -161,14 +161,15 @@ public UserTaskInstance update(UserTaskInstance userTaskInstance) {
}
@Override
- public UserTaskInstance remove(String userTaskInstanceId) {
+ public UserTaskInstance remove(UserTaskInstance userTaskInstance) {
try {
- if (!userTaskInstances.containsKey(userTaskInstanceId)) {
+ if (!userTaskInstances.containsKey(userTaskInstance.getId())) {
return null;
}
- return disconnectUserTaskInstance.apply(mapper.readValue(userTaskInstances.remove(userTaskInstanceId), DefaultUserTaskInstance.class));
+ userTaskInstances.remove(userTaskInstance.getId());
+ return disconnectUserTaskInstance.apply(userTaskInstance);
} catch (Exception e) {
- LOG.error("during remove {}", userTaskInstanceId, e);
+ LOG.error("during remove {}", userTaskInstance, e);
return null;
}
}
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 a28a5accfde..6af8a612fcf 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
@@ -127,9 +127,9 @@ public Optional activate(UserTaskInstance userTaskInsta
public Optional claim(UserTaskInstance userTaskInstance, UserTaskTransitionToken token, IdentityProvider identityProvider) {
if (userTaskInstance instanceof DefaultUserTaskInstance defaultUserTaskInstance) {
if (token.data().containsKey(PARAMETER_USER)) {
- defaultUserTaskInstance.setActuaOwner((String) token.data().get(PARAMETER_USER));
+ defaultUserTaskInstance.setActualOwner((String) token.data().get(PARAMETER_USER));
} else {
- defaultUserTaskInstance.setActuaOwner(identityProvider.getName());
+ defaultUserTaskInstance.setActualOwner(identityProvider.getName());
}
}
return Optional.empty();
@@ -137,7 +137,7 @@ public Optional claim(UserTaskInstance userTaskInstance
public Optional release(UserTaskInstance userTaskInstance, UserTaskTransitionToken token, IdentityProvider identityProvider) {
if (userTaskInstance instanceof DefaultUserTaskInstance defaultUserTaskInstance) {
- defaultUserTaskInstance.setActuaOwner(null);
+ defaultUserTaskInstance.setActualOwner(null);
}
return Optional.empty();
}
diff --git a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/lifecycle/DefaultUserTaskTransitionToken.java b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/lifecycle/DefaultUserTaskTransitionToken.java
index f8cfd7733fc..56561c9ca51 100644
--- a/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/lifecycle/DefaultUserTaskTransitionToken.java
+++ b/jbpm/jbpm-usertask/src/main/java/org/kie/kogito/usertask/impl/lifecycle/DefaultUserTaskTransitionToken.java
@@ -19,6 +19,7 @@
package org.kie.kogito.usertask.impl.lifecycle;
import java.util.Map;
+import java.util.Objects;
import org.kie.kogito.usertask.lifecycle.UserTaskState;
import org.kie.kogito.usertask.lifecycle.UserTaskTransitionToken;
@@ -34,7 +35,7 @@ public DefaultUserTaskTransitionToken(String transition, UserTaskState source, U
this.transition = transition;
this.source = source;
this.target = target;
- this.data = data;
+ this.data = Objects.isNull(data) ? Map.of() : data;
}
@Override
diff --git a/kogito-bom/pom.xml b/kogito-bom/pom.xml
index 6e4af07c46d..13acf8c5126 100755
--- a/kogito-bom/pom.xml
+++ b/kogito-bom/pom.xml
@@ -781,18 +781,6 @@
sources
-
- org.kie
- kie-addons-springboot-flyway
- ${project.version}
-
-
- org.kie
- kie-addons-springboot-flyway
- ${project.version}
- sources
-
-
org.kie.kogito
@@ -1732,6 +1720,59 @@
sources
+
+
+ org.jbpm
+ jbpm-addons-usertask-storage-jpa
+ ${project.version}
+
+
+ org.jbpm
+ jbpm-addons-usertask-storage-jpa
+ ${project.version}
+ sources
+
+
+ org.jbpm
+ jbpm-addons-usertask-storage-jpa
+ ${project.version}
+ test-jar
+ test
+
+
+ org.jbpm
+ jbpm-addons-quarkus-usertask-storage-jpa
+ ${project.version}
+
+
+ org.jbpm
+ jbpm-addons-quarkus-usertask-storage-jpa
+ ${project.version}
+ sources
+
+
+ org.jbpm
+ jbpm-addons-quarkus-usertask-storage-jpa-deployment
+ ${project.version}
+
+
+ org.jbpm
+ jbpm-addons-quarkus-usertask-storage-jpa-deployment
+ ${project.version}
+ sources
+
+
+ org.jbpm
+ jbpm-addons-springboot-usertask-storage-jpa
+ ${project.version}
+
+
+ org.jbpm
+ jbpm-addons-springboot-usertask-storage-jpa
+ ${project.version}
+ sources
+
+
org.kie
@@ -1766,7 +1807,17 @@
${project.version}
sources
-
+
+ org.kie
+ kie-addons-springboot-flyway
+ ${project.version}
+
+
+ org.kie
+ kie-addons-springboot-flyway
+ ${project.version}
+ sources
+
diff --git a/kogito-build/kogito-dependencies-bom/pom.xml b/kogito-build/kogito-dependencies-bom/pom.xml
index 26fae00aa0d..f085d103e0a 100644
--- a/kogito-build/kogito-dependencies-bom/pom.xml
+++ b/kogito-build/kogito-dependencies-bom/pom.xml
@@ -108,6 +108,8 @@
7.10.2
3.1.0
+ 3.1.0
+
6.2.7.Final
24.0.4
@@ -443,6 +445,12 @@
${version.jakarta.ws.rs}
+
+ jakarta.persistence
+ jakarta.persistence-api
+ ${version.jakarta.persistence-api}
+
+
org.junit.jupiter
junit-jupiter-api
diff --git a/quarkus/addons/flyway/deployment/pom.xml b/quarkus/addons/flyway/deployment/pom.xml
index 1e6eefccdc8..de160bad027 100644
--- a/quarkus/addons/flyway/deployment/pom.xml
+++ b/quarkus/addons/flyway/deployment/pom.xml
@@ -50,6 +50,7 @@
io.quarkus
quarkus-agroal-deployment
+
org.mockito
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/deployment/pom.xml b/quarkus/addons/jbpm-usertask-storage-jpa/deployment/pom.xml
new file mode 100644
index 00000000000..fe131a6e27f
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/deployment/pom.xml
@@ -0,0 +1,110 @@
+
+
+
+
+ jbpm-addon-quarkus-usertask-storage-jpa-parent
+ org.jbpm
+ 999-SNAPSHOT
+
+ 4.0.0
+
+ jbpm-addons-quarkus-usertask-storage-jpa-deployment
+ jBPM :: Add-Ons :: Quarkus :: User Task Storage JPA :: Deployment
+ jBPM Add-On Quarkus User Task Storage JPA Deployment
+
+
+ UTF-8
+ org.jbpm.usertask.storage.jpa.quarkus.deployment
+
+
+
+
+ org.jbpm
+ jbpm-addons-quarkus-usertask-storage-jpa
+
+
+ org.kie
+ kogito-addons-quarkus-common-deployment
+
+
+ org.kie
+ kie-addons-quarkus-flyway-deployment
+
+
+
+ io.quarkus
+ quarkus-arc-deployment
+
+
+ io.quarkus
+ quarkus-agroal-deployment
+
+
+ io.quarkus
+ quarkus-hibernate-orm-deployment
+
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+ io.quarkus
+ quarkus-junit5-mockito
+ test
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${version.io.quarkus}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/deployment/src/main/java/org/jbpm/usertask/storage/jpa/quarkus/deployment/JBPMUserTaskStorageJPAExtensionProcessor.java b/quarkus/addons/jbpm-usertask-storage-jpa/deployment/src/main/java/org/jbpm/usertask/storage/jpa/quarkus/deployment/JBPMUserTaskStorageJPAExtensionProcessor.java
new file mode 100644
index 00000000000..6d7087a4716
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/deployment/src/main/java/org/jbpm/usertask/storage/jpa/quarkus/deployment/JBPMUserTaskStorageJPAExtensionProcessor.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.storage.jpa.quarkus.deployment;
+
+import org.kie.kogito.quarkus.addons.common.deployment.KogitoCapability;
+import org.kie.kogito.quarkus.addons.common.deployment.RequireCapabilityKogitoAddOnProcessor;
+
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.builditem.FeatureBuildItem;
+
+public class JBPMUserTaskStorageJPAExtensionProcessor extends RequireCapabilityKogitoAddOnProcessor {
+
+ private static final String FEATURE = "jbpm-addon-usertask-storage-jpa";
+
+ public JBPMUserTaskStorageJPAExtensionProcessor() {
+ super(KogitoCapability.PROCESSES);
+ }
+
+ @BuildStep
+ FeatureBuildItem feature() {
+ return new FeatureBuildItem(FEATURE);
+ }
+}
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/pom.xml b/quarkus/addons/jbpm-usertask-storage-jpa/pom.xml
new file mode 100644
index 00000000000..1fc1709a355
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/pom.xml
@@ -0,0 +1,45 @@
+
+
+
+
+ kogito-addons-quarkus-parent
+ org.kie
+ 999-SNAPSHOT
+
+ 4.0.0
+
+ org.jbpm
+ jbpm-addon-quarkus-usertask-storage-jpa-parent
+
+ jBPM :: Add-Ons :: Quarkus :: User Task Storage JPA :: Parent
+ jBPM Add-On Quarkus User Task Storage JPA Parent
+
+ pom
+
+
+ deployment
+ runtime
+
+
+
\ No newline at end of file
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/pom.xml b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/pom.xml
new file mode 100644
index 00000000000..e7808950852
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/pom.xml
@@ -0,0 +1,170 @@
+
+
+
+
+ jbpm-addon-quarkus-usertask-storage-jpa-parent
+ org.jbpm
+ 999-SNAPSHOT
+
+ 4.0.0
+
+ jbpm-addons-quarkus-usertask-storage-jpa
+ jBPM :: Add-Ons :: Quarkus :: User Task Storage JPA :: Runtime
+ jBPM Add-On Quarkus User Task Storage JPA Runtime
+
+
+ UTF-8
+ org.jbpm.usertask.storage.jpa.quarkus
+
+
+
+
+ org.jbpm
+ jbpm-addons-usertask-storage-jpa
+
+
+ org.kie
+ kie-addons-quarkus-flyway
+
+
+ io.quarkus
+ quarkus-arc
+
+
+ io.quarkus
+ quarkus-hibernate-orm
+
+
+
+ io.quarkus
+ quarkus-agroal
+ test
+
+
+ io.quarkus
+ quarkus-jdbc-postgresql
+ test
+
+
+ io.quarkus
+ quarkus-jdbc-h2
+ test
+
+
+ io.quarkus
+ quarkus-test-h2
+ test
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ io.quarkus
+ quarkus-junit5-mockito
+ test
+
+
+ org.kie.kogito
+ kogito-quarkus-test-utils
+ test
+
+
+ org.jbpm
+ jbpm-addons-usertask-storage-jpa
+ test-jar
+ test
+
+
+
+
+
+ io.quarkus
+ quarkus-extension-maven-plugin
+ ${version.io.quarkus}
+
+
+ compile
+
+ extension-descriptor
+
+
+ ${project.groupId}:${project.artifactId}-deployment:${project.version}
+
+ org.jbpm.addons.usertask.storage
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${version.io.quarkus}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+ org.jboss.logmanager.LogManager
+
+
+
+
+ default
+
+ integration-test
+
+
+
+ verify
+
+ verify
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/QuarkusJPAUserTaskInstances.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/QuarkusJPAUserTaskInstances.java
new file mode 100644
index 00000000000..d1cdbef5eb4
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/QuarkusJPAUserTaskInstances.java
@@ -0,0 +1,42 @@
+/*
+ * 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.usertask.jpa.quarkus;
+
+import org.jbpm.usertask.jpa.JPAUserTaskInstances;
+import org.jbpm.usertask.jpa.mapper.UserTaskInstanceEntityMapper;
+import org.jbpm.usertask.jpa.repository.UserTaskInstanceRepository;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+
+@Transactional
+@ApplicationScoped
+public class QuarkusJPAUserTaskInstances extends JPAUserTaskInstances {
+
+ QuarkusJPAUserTaskInstances() {
+ super(null, null);
+ }
+
+ @Inject
+ public QuarkusJPAUserTaskInstances(UserTaskInstanceRepository userTaskInstanceRepository, UserTaskInstanceEntityMapper userTaskInstanceEntityMapper) {
+ super(userTaskInstanceRepository, userTaskInstanceEntityMapper);
+ }
+}
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusAttachmentsEntityMapper.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusAttachmentsEntityMapper.java
new file mode 100644
index 00000000000..cb642a4334c
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusAttachmentsEntityMapper.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.jbpm.usertask.jpa.quarkus.mapper;
+
+import org.jbpm.usertask.jpa.mapper.AttachmentsEntityMapper;
+import org.jbpm.usertask.jpa.repository.AttachmentRepository;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+@ApplicationScoped
+public class QuarkusAttachmentsEntityMapper extends AttachmentsEntityMapper {
+
+ QuarkusAttachmentsEntityMapper() {
+ this(null);
+ }
+
+ @Inject
+ public QuarkusAttachmentsEntityMapper(AttachmentRepository attachmentRepository) {
+ super(attachmentRepository);
+ }
+}
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusCommentsEntityMapper.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusCommentsEntityMapper.java
new file mode 100644
index 00000000000..0024b10a31e
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusCommentsEntityMapper.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.jbpm.usertask.jpa.quarkus.mapper;
+
+import org.jbpm.usertask.jpa.mapper.CommentsEntityMapper;
+import org.jbpm.usertask.jpa.repository.CommentRepository;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+@ApplicationScoped
+public class QuarkusCommentsEntityMapper extends CommentsEntityMapper {
+
+ public QuarkusCommentsEntityMapper() {
+ this(null);
+ }
+
+ @Inject
+ public QuarkusCommentsEntityMapper(CommentRepository commentRepository) {
+ super(commentRepository);
+ }
+}
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusTaskInputsEntityMapper.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusTaskInputsEntityMapper.java
new file mode 100644
index 00000000000..1304468655e
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusTaskInputsEntityMapper.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.jbpm.usertask.jpa.quarkus.mapper;
+
+import org.jbpm.usertask.jpa.mapper.TaskInputsEntityMapper;
+import org.jbpm.usertask.jpa.repository.TaskInputRepository;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+@ApplicationScoped
+public class QuarkusTaskInputsEntityMapper extends TaskInputsEntityMapper {
+
+ public QuarkusTaskInputsEntityMapper() {
+ this(null);
+ }
+
+ @Inject
+ public QuarkusTaskInputsEntityMapper(TaskInputRepository repository) {
+ super(repository);
+ }
+}
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusTaskMetadataEntityMapper.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusTaskMetadataEntityMapper.java
new file mode 100644
index 00000000000..fdc5b94360a
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusTaskMetadataEntityMapper.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.jbpm.usertask.jpa.quarkus.mapper;
+
+import org.jbpm.usertask.jpa.mapper.TaskMetadataEntityMapper;
+import org.jbpm.usertask.jpa.repository.TaskMetadataRepository;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+@ApplicationScoped
+public class QuarkusTaskMetadataEntityMapper extends TaskMetadataEntityMapper {
+
+ public QuarkusTaskMetadataEntityMapper() {
+ this(null);
+ }
+
+ @Inject
+ public QuarkusTaskMetadataEntityMapper(TaskMetadataRepository repository) {
+ super(repository);
+ }
+}
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusTaskOutputsEntityMapper.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusTaskOutputsEntityMapper.java
new file mode 100644
index 00000000000..0d80b25e24b
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusTaskOutputsEntityMapper.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.jbpm.usertask.jpa.quarkus.mapper;
+
+import org.jbpm.usertask.jpa.mapper.TaskOutputsEntityMapper;
+import org.jbpm.usertask.jpa.repository.TaskOutputRepository;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+@ApplicationScoped
+public class QuarkusTaskOutputsEntityMapper extends TaskOutputsEntityMapper {
+
+ public QuarkusTaskOutputsEntityMapper() {
+ this(null);
+ }
+
+ @Inject
+ public QuarkusTaskOutputsEntityMapper(TaskOutputRepository repository) {
+ super(repository);
+ }
+}
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusUserTaskInstanceEntityMapper.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusUserTaskInstanceEntityMapper.java
new file mode 100644
index 00000000000..e3f6c79e1ad
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/mapper/QuarkusUserTaskInstanceEntityMapper.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.jbpm.usertask.jpa.quarkus.mapper;
+
+import org.jbpm.usertask.jpa.mapper.*;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+@ApplicationScoped
+public class QuarkusUserTaskInstanceEntityMapper extends UserTaskInstanceEntityMapper {
+
+ QuarkusUserTaskInstanceEntityMapper() {
+ this(null, null, null, null, null);
+ }
+
+ @Inject
+ public QuarkusUserTaskInstanceEntityMapper(AttachmentsEntityMapper attachmentsMapper, CommentsEntityMapper commentsMapper, TaskMetadataEntityMapper taskMetadataEntityMapper,
+ TaskInputsEntityMapper taskInputsEntityMapper, TaskOutputsEntityMapper taskOutputsEntityMapper) {
+ super(attachmentsMapper, commentsMapper, taskMetadataEntityMapper, taskInputsEntityMapper, taskOutputsEntityMapper);
+ }
+}
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusAttachmentRepository.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusAttachmentRepository.java
new file mode 100644
index 00000000000..04a551660d5
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusAttachmentRepository.java
@@ -0,0 +1,41 @@
+/*
+ * 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.usertask.jpa.quarkus.repository;
+
+import org.jbpm.usertask.jpa.repository.AttachmentRepository;
+import org.jbpm.usertask.jpa.repository.UserTaskJPAContext;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+
+@ApplicationScoped
+@Transactional
+public class QuarkusAttachmentRepository extends AttachmentRepository {
+
+ QuarkusAttachmentRepository() {
+ this(null);
+ }
+
+ @Inject
+ public QuarkusAttachmentRepository(UserTaskJPAContext context) {
+ super(context);
+ }
+}
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusCommentRepository.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusCommentRepository.java
new file mode 100644
index 00000000000..7bfecdb4c2e
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusCommentRepository.java
@@ -0,0 +1,41 @@
+/*
+ * 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.usertask.jpa.quarkus.repository;
+
+import org.jbpm.usertask.jpa.repository.CommentRepository;
+import org.jbpm.usertask.jpa.repository.UserTaskJPAContext;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+
+@ApplicationScoped
+@Transactional
+public class QuarkusCommentRepository extends CommentRepository {
+
+ QuarkusCommentRepository() {
+ this(null);
+ }
+
+ @Inject
+ public QuarkusCommentRepository(UserTaskJPAContext context) {
+ super(context);
+ }
+}
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusTaskInputEntityRepository.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusTaskInputEntityRepository.java
new file mode 100644
index 00000000000..41818d7e0c7
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusTaskInputEntityRepository.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.quarkus.repository;
+
+import org.jbpm.usertask.jpa.repository.TaskInputRepository;
+import org.jbpm.usertask.jpa.repository.UserTaskJPAContext;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+@ApplicationScoped
+public class QuarkusTaskInputEntityRepository extends TaskInputRepository {
+
+ QuarkusTaskInputEntityRepository() {
+ this(null);
+ }
+
+ @Inject
+ public QuarkusTaskInputEntityRepository(UserTaskJPAContext context) {
+ super(context);
+ }
+
+}
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusTaskMetadataEntityRepository.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusTaskMetadataEntityRepository.java
new file mode 100644
index 00000000000..6ff95bdf79c
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusTaskMetadataEntityRepository.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.quarkus.repository;
+
+import org.jbpm.usertask.jpa.repository.TaskMetadataRepository;
+import org.jbpm.usertask.jpa.repository.UserTaskJPAContext;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+@ApplicationScoped
+public class QuarkusTaskMetadataEntityRepository extends TaskMetadataRepository {
+
+ QuarkusTaskMetadataEntityRepository() {
+ this(null);
+ }
+
+ @Inject
+ public QuarkusTaskMetadataEntityRepository(UserTaskJPAContext context) {
+ super(context);
+ }
+
+}
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusTaskOutputEntityRepository.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusTaskOutputEntityRepository.java
new file mode 100644
index 00000000000..7810b987848
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusTaskOutputEntityRepository.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.quarkus.repository;
+
+import org.jbpm.usertask.jpa.repository.TaskOutputRepository;
+import org.jbpm.usertask.jpa.repository.UserTaskJPAContext;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+
+@ApplicationScoped
+public class QuarkusTaskOutputEntityRepository extends TaskOutputRepository {
+
+ QuarkusTaskOutputEntityRepository() {
+ this(null);
+ }
+
+ @Inject
+ public QuarkusTaskOutputEntityRepository(UserTaskJPAContext context) {
+ super(context);
+ }
+
+}
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusUserTaskInstanceRepository.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusUserTaskInstanceRepository.java
new file mode 100644
index 00000000000..2a487fb8bfb
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusUserTaskInstanceRepository.java
@@ -0,0 +1,41 @@
+/*
+ * 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.usertask.jpa.quarkus.repository;
+
+import org.jbpm.usertask.jpa.repository.UserTaskInstanceRepository;
+import org.jbpm.usertask.jpa.repository.UserTaskJPAContext;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.transaction.Transactional;
+
+@ApplicationScoped
+@Transactional
+public class QuarkusUserTaskInstanceRepository extends UserTaskInstanceRepository {
+
+ QuarkusUserTaskInstanceRepository() {
+ this(null);
+ }
+
+ @Inject
+ public QuarkusUserTaskInstanceRepository(UserTaskJPAContext context) {
+ super(context);
+ }
+}
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusUserTaskJPAContext.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusUserTaskJPAContext.java
new file mode 100644
index 00000000000..bcbfb1e366b
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/java/org/jbpm/usertask/jpa/quarkus/repository/QuarkusUserTaskJPAContext.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.quarkus.repository;
+
+import org.jbpm.usertask.jpa.repository.UserTaskJPAContext;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.PersistenceContext;
+import jakarta.transaction.Transactional;
+
+@ApplicationScoped
+@Transactional
+public class QuarkusUserTaskJPAContext implements UserTaskJPAContext {
+
+ @PersistenceContext
+ private EntityManager em;
+
+ @Override
+ public EntityManager getEntityManager() {
+ return em;
+ }
+}
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/resources/META-INF/beans.xml b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/resources/META-INF/beans.xml
new file mode 100644
index 00000000000..a0eb9fbf8cd
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,20 @@
+
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/resources/META-INF/quarkus-extension.yaml
new file mode 100644
index 00000000000..4983be32275
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -0,0 +1,37 @@
+#
+# 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.
+#
+
+name: jBPM User Task Storage JPS
+description: Add-On that enables JPA storage for jBPM User Tasks
+metadata:
+ keywords:
+ - KIE
+ - jBPM
+ - persistence
+ - JPA
+ guide: https://quarkus.io/guides/kie
+ categories:
+ - "business-automation"
+ - "cloud"
+ - "jBPM"
+ - "persistence"
+ - "JPA"
+ status: "stable"
+ config:
+ - "org.jbpm"
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/test/java/org/jbpm/usertask/jpa/quarkus/BaseQuarkusJPAUserTaskInstancesTest.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/test/java/org/jbpm/usertask/jpa/quarkus/BaseQuarkusJPAUserTaskInstancesTest.java
new file mode 100644
index 00000000000..1bfcc273af6
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/test/java/org/jbpm/usertask/jpa/quarkus/BaseQuarkusJPAUserTaskInstancesTest.java
@@ -0,0 +1,462 @@
+/*
+ * 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.usertask.jpa.quarkus;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.*;
+import java.util.function.Function;
+
+import org.assertj.core.api.Assertions;
+import org.jbpm.usertask.jpa.JPAUserTaskInstances;
+import org.jbpm.usertask.jpa.mapper.utils.TestUtils;
+import org.jbpm.usertask.jpa.model.UserTaskInstanceEntity;
+import org.jbpm.usertask.jpa.quarkus.repository.QuarkusUserTaskJPAContext;
+import org.jbpm.usertask.jpa.repository.*;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.kie.kogito.auth.IdentityProviders;
+import org.kie.kogito.usertask.UserTaskInstance;
+import org.kie.kogito.usertask.impl.DefaultUserTaskInstance;
+import org.kie.kogito.usertask.model.Attachment;
+import org.kie.kogito.usertask.model.Comment;
+import org.mockito.Mockito;
+
+import jakarta.inject.Inject;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+public abstract class BaseQuarkusJPAUserTaskInstancesTest {
+
+ @Inject
+ QuarkusUserTaskJPAContext context;
+
+ @Inject
+ JPAUserTaskInstances userTaskInstances;
+
+ @Inject
+ UserTaskInstanceRepository userTaskInstanceRepository;
+
+ @Inject
+ AttachmentRepository attachmentRepository;
+
+ @Inject
+ CommentRepository commentRepository;
+
+ private Function connect;
+ private Function disconnect;
+
+ @BeforeEach
+ public void init() {
+ connect = Mockito.mock(Function.class);
+ disconnect = Mockito.mock(Function.class);
+
+ when(connect.apply(any(UserTaskInstance.class))).thenAnswer(i -> i.getArgument(0));
+ when(disconnect.apply(any(UserTaskInstance.class))).thenAnswer(i -> i.getArgument(0));
+
+ userTaskInstances.setReconnectUserTaskInstance(connect);
+ userTaskInstances.setDisconnectUserTaskInstance(disconnect);
+ }
+
+ @Test
+ public void testCreateUserTask() {
+ UserTaskInstance instance = createUserTaskInstance();
+
+ Assertions.assertThat(userTaskInstances.exists(instance.getId()))
+ .isFalse();
+
+ Assertions.assertThat(userTaskInstances.findById(instance.getId()))
+ .isNotNull()
+ .isEmpty();
+
+ userTaskInstances.create(instance);
+
+ verify(connect, times(1)).apply(any(UserTaskInstance.class));
+
+ Optional entityOptional = userTaskInstanceRepository.findById(instance.getId());
+
+ Assertions.assertThat(entityOptional)
+ .isNotNull()
+ .isPresent();
+
+ UserTaskInstanceEntity entity = entityOptional.get();
+
+ assertEntityAndInstance(entity, instance);
+
+ Optional persistedInstanceOptional = userTaskInstances.findById(instance.getId());
+
+ Assertions.assertThat(persistedInstanceOptional)
+ .isNotNull()
+ .isPresent();
+
+ assertEntityAndInstance(entity, persistedInstanceOptional.get());
+ }
+
+ @Test
+ public void testCreateExistingTask() {
+ UserTaskInstance instance = createUserTaskInstance();
+
+ userTaskInstances.create(instance);
+
+ Assertions.assertThatThrownBy(() -> userTaskInstances.create(instance))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Task Already exists.");
+
+ userTaskInstances.remove(instance);
+
+ Assertions.assertThat(userTaskInstances.exists(instance.getId()))
+ .isFalse();
+ }
+
+ @Test
+ public void testEditTaskInputOutputs() {
+
+ UserTaskInstance instance = createUserTaskInstance();
+
+ userTaskInstances.create(instance);
+
+ Optional entityOptional = userTaskInstanceRepository.findById(instance.getId());
+
+ Assertions.assertThat(entityOptional)
+ .isNotNull()
+ .isPresent();
+
+ UserTaskInstanceEntity entity = entityOptional.get();
+
+ Assertions.assertThat(entity.getInputs())
+ .hasSize(instance.getInputs().size());
+
+ Assertions.assertThat(entity.getOutputs())
+ .hasSize(instance.getOutputs().size());
+
+ instance.getInputs().clear();
+ instance.setInput("new_input", "this is a new input");
+
+ instance.getOutputs().clear();
+ instance.setOutput("new_output", "this is a new output");
+
+ userTaskInstances.update(instance);
+
+ entity = userTaskInstanceRepository.findById(instance.getId()).get();
+
+ Assertions.assertThat(entity.getInputs())
+ .hasSize(1);
+
+ Assertions.assertThat(entity.getOutputs())
+ .hasSize(1);
+
+ TestUtils.assertUserTaskEntityInputs(entity, instance);
+ TestUtils.assertUserTaskEntityOutputs(entity, instance);
+
+ userTaskInstances.remove(instance);
+
+ Assertions.assertThat(userTaskInstances.exists(instance.getId()))
+ .isFalse();
+
+ }
+
+ @Test
+ public void testFindByIdentityByActualOwner() {
+
+ UserTaskInstance instance = createUserTaskInstance();
+
+ userTaskInstances.create(instance);
+
+ List result = userTaskInstances.findByIdentity(IdentityProviders.of("Homer", "Group"));
+
+ Assertions.assertThat(result)
+ .hasSize(1);
+
+ verify(connect, times(2)).apply(any(UserTaskInstance.class));
+
+ userTaskInstances.remove(instance);
+
+ Assertions.assertThat(userTaskInstances.exists(instance.getId()))
+ .isFalse();
+ }
+
+ @Test
+ public void testFindByIdentityByPotentialOwners() {
+ UserTaskInstance instance = createUserTaskInstance();
+
+ userTaskInstances.create(instance);
+
+ List result = userTaskInstances.findByIdentity(IdentityProviders.of("Liza", "Group"));
+
+ Assertions.assertThat(result)
+ .hasSize(1);
+
+ verify(connect, times(2)).apply(any(UserTaskInstance.class));
+
+ List result2 = userTaskInstances.findByIdentity(IdentityProviders.of("Bart", "Simpson"));
+
+ Assertions.assertThat(result2)
+ .hasSize(1);
+
+ verify(connect, times(3)).apply(any(UserTaskInstance.class));
+
+ userTaskInstances.remove(instance);
+
+ Assertions.assertThat(userTaskInstances.exists(instance.getId()))
+ .isFalse();
+ }
+
+ @Test
+ public void testFindByIdentityByPotentialGroups() {
+ UserTaskInstance instance = createUserTaskInstance();
+
+ userTaskInstances.create(instance);
+
+ List result = userTaskInstances.findByIdentity(IdentityProviders.of("Abraham", "Admin", "Simpson"));
+
+ Assertions.assertThat(result)
+ .hasSize(1);
+
+ verify(connect, times(2)).apply(any(UserTaskInstance.class));
+
+ userTaskInstances.remove(instance);
+
+ Assertions.assertThat(userTaskInstances.exists(instance.getId()))
+ .isFalse();
+ }
+
+ @Test
+ public void testFindByIdentityByAdminUsers() {
+
+ UserTaskInstance instance = createUserTaskInstance();
+
+ userTaskInstances.create(instance);
+
+ List result = userTaskInstances.findByIdentity(IdentityProviders.of("Seymour", "Group"));
+
+ Assertions.assertThat(result)
+ .hasSize(1);
+
+ verify(connect, times(2)).apply(any(UserTaskInstance.class));
+
+ userTaskInstances.remove(instance);
+
+ Assertions.assertThat(userTaskInstances.exists(instance.getId()))
+ .isFalse();
+ }
+
+ @Test
+ public void testFindByIdentityByAdminGroups() {
+ UserTaskInstance instance = createUserTaskInstance();
+
+ userTaskInstances.create(instance);
+
+ List result = userTaskInstances.findByIdentity(IdentityProviders.of("Abraham", "Administrator", "Managers"));
+
+ Assertions.assertThat(result)
+ .hasSize(1);
+
+ verify(connect, times(2)).apply(any(UserTaskInstance.class));
+
+ userTaskInstances.remove(instance);
+
+ Assertions.assertThat(userTaskInstances.exists(instance.getId()))
+ .isFalse();
+ }
+
+ @Test
+ public void testFindByIdentityByExcludedUser() {
+ UserTaskInstance instance = createUserTaskInstance();
+
+ userTaskInstances.create(instance);
+
+ List result = userTaskInstances.findByIdentity(IdentityProviders.of("Ned"));
+
+ Assertions.assertThat(result)
+ .hasSize(0);
+
+ verify(connect, times(1)).apply(any(UserTaskInstance.class));
+
+ result = userTaskInstances.findByIdentity(IdentityProviders.of("Bart"));
+
+ Assertions.assertThat(result)
+ .hasSize(0);
+
+ verify(connect, times(1)).apply(any(UserTaskInstance.class));
+
+ userTaskInstances.remove(instance);
+
+ Assertions.assertThat(userTaskInstances.exists(instance.getId()))
+ .isFalse();
+ }
+
+ @Test
+ public void testFindByIdentityByUnknownUser() {
+ UserTaskInstance instance = createUserTaskInstance();
+
+ userTaskInstances.create(instance);
+
+ List result = userTaskInstances.findByIdentity(IdentityProviders.of("Someone", "Group"));
+
+ Assertions.assertThat(result)
+ .hasSize(0);
+
+ verify(connect, times(1)).apply(any(UserTaskInstance.class));
+
+ userTaskInstances.remove(instance);
+
+ Assertions.assertThat(userTaskInstances.exists(instance.getId()))
+ .isFalse();
+ }
+
+ @Test
+ public void testAttachments() throws URISyntaxException {
+ UserTaskInstance instance = createUserTaskInstance();
+
+ userTaskInstances.create(instance);
+
+ Optional entityOptional = userTaskInstanceRepository.findById(instance.getId());
+
+ Assertions.assertThat(entityOptional)
+ .isNotNull()
+ .isPresent();
+
+ assertEntityAndInstance(entityOptional.get(), instance);
+
+ Attachment attachment = new Attachment("1", "Admin");
+ attachment.setName("attachment 1");
+ attachment.setContent(new URI("http://url.com/to/my/attachment"));
+ attachment.setUpdatedAt(new Date());
+
+ instance.addAttachment(attachment);
+
+ entityOptional = userTaskInstanceRepository.findById(instance.getId());
+
+ TestUtils.assertUserTaskEntityAttachments(entityOptional.get().getAttachments(), instance.getAttachments());
+
+ Attachment attachment2 = new Attachment("2", "Admin");
+ attachment2.setName("attachment 2");
+ attachment2.setContent(new URI("http://url.com/to/my/attachment2"));
+ attachment2.setUpdatedAt(new Date());
+
+ instance.addAttachment(attachment2);
+
+ entityOptional = userTaskInstanceRepository.findById(instance.getId());
+ TestUtils.assertUserTaskEntityAttachments(entityOptional.get().getAttachments(), instance.getAttachments());
+
+ instance.removeAttachment(attachment);
+ instance.removeAttachment(attachment2);
+
+ userTaskInstances.update(instance);
+
+ entityOptional = userTaskInstanceRepository.findById(instance.getId());
+
+ Assertions.assertThat(entityOptional.get().getAttachments())
+ .isEmpty();
+
+ Assertions.assertThat(attachmentRepository.findAll())
+ .isEmpty();
+
+ userTaskInstances.remove(instance);
+
+ Assertions.assertThat(userTaskInstances.exists(instance.getId()))
+ .isFalse();
+ }
+
+ @Test
+ public void testComments() {
+ UserTaskInstance instance = createUserTaskInstance();
+
+ userTaskInstances.create(instance);
+
+ Optional entityOptional = userTaskInstanceRepository.findById(instance.getId());
+
+ Assertions.assertThat(entityOptional)
+ .isNotNull()
+ .isPresent();
+
+ assertEntityAndInstance(entityOptional.get(), instance);
+
+ Comment comment = new Comment("1", "Admin");
+ comment.setContent("This the comment 1");
+ comment.setUpdatedAt(new Date());
+
+ instance.addComment(comment);
+
+ entityOptional = userTaskInstanceRepository.findById(instance.getId());
+
+ UserTaskInstanceEntity userTaskInstanceEntity = entityOptional.get();
+
+ Assertions.assertThat(userTaskInstanceEntity.getComments())
+ .hasSize(1);
+
+ TestUtils.assertUserTaskEntityComments(entityOptional.get().getComments(), instance.getComments());
+
+ Comment comment2 = new Comment("2", "Admin");
+ comment2.setContent("This the comment 2");
+ comment2.setUpdatedAt(new Date());
+
+ instance.addComment(comment2);
+
+ entityOptional = userTaskInstanceRepository.findById(instance.getId());
+
+ userTaskInstanceEntity = entityOptional.get();
+
+ Assertions.assertThat(userTaskInstanceEntity.getComments())
+ .hasSize(2);
+
+ TestUtils.assertUserTaskEntityComments(userTaskInstanceEntity.getComments(), instance.getComments());
+
+ instance.removeComment(comment);
+ instance.removeComment(comment2);
+
+ entityOptional = userTaskInstanceRepository.findById(instance.getId());
+
+ Assertions.assertThat(entityOptional.get().getComments())
+ .isEmpty();
+
+ Assertions.assertThat(commentRepository.findAll())
+ .isEmpty();
+
+ userTaskInstances.remove(instance);
+
+ Assertions.assertThat(userTaskInstances.exists(instance.getId()))
+ .isFalse();
+ }
+
+ private void assertEntityAndInstance(UserTaskInstanceEntity entity, UserTaskInstance instance) {
+ TestUtils.assertUserTaskEntityData(entity, instance);
+
+ TestUtils.assertUserTaskEntityPotentialUserAndGroups(entity, instance);
+ TestUtils.assertUserTaskEntityAdminUserAndGroups(entity, instance);
+ TestUtils.assertUserTaskEntityExcludedUsers(entity, instance);
+
+ TestUtils.assertUserTaskEntityInputs(entity, instance);
+ TestUtils.assertUserTaskEntityOutputs(entity, instance);
+
+ TestUtils.assertUserTaskEntityAttachments(entity.getAttachments(), instance.getAttachments());
+ TestUtils.assertUserTaskEntityComments(entity.getComments(), instance.getComments());
+ TestUtils.assertUserTaskEntityMetadata(entity, instance);
+ }
+
+ private UserTaskInstance createUserTaskInstance() {
+ DefaultUserTaskInstance instance = TestUtils.createUserTaskInstance();
+
+ instance.setInstances(userTaskInstances);
+
+ return instance;
+ }
+}
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/test/java/org/jbpm/usertask/jpa/quarkus/H2QuarkusJPAUserTaskInstancesTest.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/test/java/org/jbpm/usertask/jpa/quarkus/H2QuarkusJPAUserTaskInstancesTest.java
new file mode 100644
index 00000000000..f314b629be5
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/test/java/org/jbpm/usertask/jpa/quarkus/H2QuarkusJPAUserTaskInstancesTest.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.jbpm.usertask.jpa.quarkus;
+
+import io.quarkus.test.TestTransaction;
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.h2.H2DatabaseTestResource;
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.TestProfile;
+
+@QuarkusTest
+@TestTransaction
+@QuarkusTestResource(value = H2DatabaseTestResource.class, restrictToAnnotatedClass = true)
+@TestProfile(H2QuarkusTestProfile.class)
+public class H2QuarkusJPAUserTaskInstancesTest extends BaseQuarkusJPAUserTaskInstancesTest {
+}
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/test/java/org/jbpm/usertask/jpa/quarkus/H2QuarkusTestProfile.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/test/java/org/jbpm/usertask/jpa/quarkus/H2QuarkusTestProfile.java
new file mode 100644
index 00000000000..4a1b05ffec8
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/test/java/org/jbpm/usertask/jpa/quarkus/H2QuarkusTestProfile.java
@@ -0,0 +1,30 @@
+/*
+ * 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.usertask.jpa.quarkus;
+
+import io.quarkus.test.junit.QuarkusTestProfile;
+
+public class H2QuarkusTestProfile implements QuarkusTestProfile {
+
+ @Override
+ public String getConfigProfile() {
+ return "test-h2";
+ }
+}
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/test/java/org/jbpm/usertask/jpa/quarkus/PostgreSQLQuarkusJPAUserTaskInstancesTest.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/test/java/org/jbpm/usertask/jpa/quarkus/PostgreSQLQuarkusJPAUserTaskInstancesTest.java
new file mode 100644
index 00000000000..7174ee048c3
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/test/java/org/jbpm/usertask/jpa/quarkus/PostgreSQLQuarkusJPAUserTaskInstancesTest.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.quarkus;
+
+import org.kie.kogito.testcontainers.quarkus.PostgreSqlQuarkusTestResource;
+
+import io.quarkus.test.TestTransaction;
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.TestProfile;
+
+@QuarkusTest
+@TestTransaction
+@QuarkusTestResource(value = PostgreSqlQuarkusTestResource.class, restrictToAnnotatedClass = true)
+@TestProfile(PostgreSQLQuarkusTestProfile.class)
+public class PostgreSQLQuarkusJPAUserTaskInstancesTest extends BaseQuarkusJPAUserTaskInstancesTest {
+
+}
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/test/java/org/jbpm/usertask/jpa/quarkus/PostgreSQLQuarkusTestProfile.java b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/test/java/org/jbpm/usertask/jpa/quarkus/PostgreSQLQuarkusTestProfile.java
new file mode 100644
index 00000000000..dda90d34319
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/test/java/org/jbpm/usertask/jpa/quarkus/PostgreSQLQuarkusTestProfile.java
@@ -0,0 +1,30 @@
+/*
+ * 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.usertask.jpa.quarkus;
+
+import io.quarkus.test.junit.QuarkusTestProfile;
+
+public class PostgreSQLQuarkusTestProfile implements QuarkusTestProfile {
+
+ @Override
+ public String getConfigProfile() {
+ return "test-postgresql";
+ }
+}
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/test/resources/META-INF/beans.xml b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/test/resources/META-INF/beans.xml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/test/resources/application.properties b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/test/resources/application.properties
new file mode 100644
index 00000000000..62677c92319
--- /dev/null
+++ b/quarkus/addons/jbpm-usertask-storage-jpa/runtime/src/test/resources/application.properties
@@ -0,0 +1,28 @@
+#
+# 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.
+#
+
+
+%test-postgresql.quarkus.datasource.db-kind=postgresql
+%test-postgresql.quarkus.datasource.devservices.enabled=false
+
+%test-h2.quarkus.datasource.db-kind=h2
+%test-h2.quarkus.datasource.username=kogito
+%test-h2.quarkus.datasource.jdbc.url=jdbc:h2:mem:default
+
+kie.flyway.enabled=true
diff --git a/quarkus/addons/pom.xml b/quarkus/addons/pom.xml
index 2a51d08ca82..de5f6685ed0 100644
--- a/quarkus/addons/pom.xml
+++ b/quarkus/addons/pom.xml
@@ -60,6 +60,7 @@
marshallers
process-definitions
dynamic
+ jbpm-usertask-storage-jpa
diff --git a/quarkus/integration-tests/integration-tests-quarkus-usertasks/pom.xml b/quarkus/integration-tests/integration-tests-quarkus-usertasks/pom.xml
new file mode 100644
index 00000000000..e648118be0c
--- /dev/null
+++ b/quarkus/integration-tests/integration-tests-quarkus-usertasks/pom.xml
@@ -0,0 +1,146 @@
+
+
+
+
+ org.kie.kogito
+ kogito-quarkus-integration-tests
+ 999-SNAPSHOT
+
+ 4.0.0
+ integration-tests-quarkus-usertasks
+ Kogito :: Integration Tests :: Quarkus :: Processes :: Source Files
+
+
+ org.jbpm.usertask.storage.jpa.quarkus.it
+
+
+
+
+
+ org.kie.kogito
+ kogito-quarkus-bom
+ ${project.version}
+ pom
+ import
+
+
+
+
+
+
+ io.quarkus
+ quarkus-resteasy
+
+
+ io.quarkus
+ quarkus-resteasy-jackson
+
+
+ org.jbpm
+ jbpm-quarkus
+
+
+ org.kie
+ kie-addons-quarkus-persistence-jdbc
+
+
+ org.jbpm
+ jbpm-addons-quarkus-usertask-storage-jpa
+
+
+ io.quarkus
+ quarkus-arc
+
+
+ io.quarkus
+ quarkus-agroal
+
+
+ io.quarkus
+ quarkus-jdbc-postgresql
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+ org.kie.kogito
+ kogito-quarkus-test-utils
+ test
+
+
+
+
+ org.jbpm
+ jbpm-quarkus-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
+
+ org.jbpm
+ jbpm-addons-quarkus-usertask-storage-jpa-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
+
+
+
+
+
+ io.quarkus
+ quarkus-maven-plugin
+
+
+
+ build
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/main/java/org/acme/travels/Address.java b/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/main/java/org/acme/travels/Address.java
new file mode 100644
index 00000000000..662dfa9b9f7
--- /dev/null
+++ b/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/main/java/org/acme/travels/Address.java
@@ -0,0 +1,76 @@
+/*
+ * 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.acme.travels;
+
+public class Address {
+
+ private String street;
+ private String city;
+ private String zipCode;
+ private String country;
+
+ public Address() {
+
+ }
+
+ public Address(String street, String city, String zipCode, String country) {
+ super();
+ this.street = street;
+ this.city = city;
+ this.zipCode = zipCode;
+ this.country = country;
+ }
+
+ public String getStreet() {
+ return street;
+ }
+
+ public void setStreet(String street) {
+ this.street = street;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public void setCity(String city) {
+ this.city = city;
+ }
+
+ public String getZipCode() {
+ return zipCode;
+ }
+
+ public void setZipCode(String zipCode) {
+ this.zipCode = zipCode;
+ }
+
+ public String getCountry() {
+ return country;
+ }
+
+ public void setCountry(String country) {
+ this.country = country;
+ }
+
+ @Override
+ public String toString() {
+ return "Address [street=" + street + ", city=" + city + ", zipCode=" + zipCode + ", country=" + country + "]";
+ }
+}
diff --git a/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/main/java/org/acme/travels/Traveller.java b/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/main/java/org/acme/travels/Traveller.java
new file mode 100644
index 00000000000..c24685803d2
--- /dev/null
+++ b/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/main/java/org/acme/travels/Traveller.java
@@ -0,0 +1,88 @@
+/*
+ * 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.acme.travels;
+
+public class Traveller {
+
+ private String firstName;
+ private String lastName;
+ private String email;
+ private String nationality;
+ private Address address;
+
+ public Traveller() {
+
+ }
+
+ public Traveller(String firstName, String lastName, String email, String nationality, Address address) {
+ super();
+ this.firstName = firstName;
+ this.lastName = lastName;
+ this.email = email;
+ this.nationality = nationality;
+ this.address = address;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getNationality() {
+ return nationality;
+ }
+
+ public void setNationality(String nationality) {
+ this.nationality = nationality;
+ }
+
+ public Address getAddress() {
+ return address;
+ }
+
+ public void setAddress(Address address) {
+ this.address = address;
+ }
+
+ @Override
+ public String toString() {
+ return "Traveller [firstName=" + firstName + ", lastName=" + lastName + ", email=" + email + ", nationality="
+ + nationality + ", address=" + address + "]";
+ }
+
+}
diff --git a/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/main/resources/application.properties b/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/main/resources/application.properties
new file mode 100644
index 00000000000..5458fa57f9c
--- /dev/null
+++ b/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/main/resources/application.properties
@@ -0,0 +1,24 @@
+#
+# 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.
+#
+
+quarkus.datasource.db-kind=postgresql
+
+kogito.persistence.type=jdbc
+
+kie.flyway.enabled=true
\ No newline at end of file
diff --git a/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/main/resources/approval.bpmn b/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/main/resources/approval.bpmn
new file mode 100644
index 00000000000..275d148f7ac
--- /dev/null
+++ b/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/main/resources/approval.bpmn
@@ -0,0 +1,304 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ _9EAFE6C1-69B4-4908-B764-EF3C4A55BEE3
+ _C13522F1-230A-4C26-B5A9-533A5D9FEE9D
+
+
+
+
+
+
+
+
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_TaskNameInputX
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_travellerInputX
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_SkippableInputX
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_GroupIdInputX
+
+
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_ActorIdOutputX
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_approvedOutputX
+
+
+
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_TaskNameInputX
+
+
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_TaskNameInputX
+
+
+
+ traveller
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_travellerInputX
+
+
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_SkippableInputX
+
+
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_SkippableInputX
+
+
+
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_GroupIdInputX
+
+
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_GroupIdInputX
+
+
+
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_ActorIdOutputX
+ approver
+
+
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_approvedOutputX
+ firstLineApproval
+
+
+
+ manager
+
+
+
+
+
+
+
+
+
+ _C13522F1-230A-4C26-B5A9-533A5D9FEE9D
+ _078F46FB-B7A1-4DBB-BE9A-75C7CB0CCD03
+
+
+
+
+
+
+
+
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_TaskNameInputX
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_ExcludedOwnerIdInputX
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_travellerInputX
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_SkippableInputX
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_GroupIdInputX
+
+
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_approvedOutputX
+
+
+
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_TaskNameInputX
+
+
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_TaskNameInputX
+
+
+
+ approver
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_ExcludedOwnerIdInputX
+
+
+ traveller
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_travellerInputX
+
+
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_SkippableInputX
+
+
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_SkippableInputX
+
+
+
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_GroupIdInputX
+
+
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_GroupIdInputX
+
+
+
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_approvedOutputX
+ secondLineApproval
+
+
+
+
+
+
+
+
+ _9EAFE6C1-69B4-4908-B764-EF3C4A55BEE3
+
+
+
+
+
+
+
+ _078F46FB-B7A1-4DBB-BE9A-75C7CB0CCD03
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ _F0jB8En5EeqlfsIhq1UCRQ
+ _F0jB8En5EeqlfsIhq1UCRQ
+
+
diff --git a/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/test/java/org/jbpm/userTask/jpa/it/BaseUserTaskIT.java b/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/test/java/org/jbpm/userTask/jpa/it/BaseUserTaskIT.java
new file mode 100644
index 00000000000..1d75f4c0c5a
--- /dev/null
+++ b/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/test/java/org/jbpm/userTask/jpa/it/BaseUserTaskIT.java
@@ -0,0 +1,69 @@
+/*
+ * 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.userTask.jpa.it;
+
+import java.util.Map;
+
+import org.acme.travels.Traveller;
+
+import io.restassured.RestAssured;
+import io.restassured.http.ContentType;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.Matchers.emptyOrNullString;
+
+public abstract class BaseUserTaskIT {
+ public static final String PROCESS_ID = "approvals";
+ public static final String USER_TASKS_ENDPOINT = "/usertasks/instance";
+ public static final String USER_TASKS_INSTANCE_ENDPOINT = USER_TASKS_ENDPOINT + "/{taskId}";
+
+ static {
+ RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
+ }
+
+ public String startProcessInstance(Traveller traveller) {
+ final String pid = given().contentType(ContentType.JSON)
+ .when()
+ .body(Map.of("traveller", traveller))
+ .post("/{processId}", PROCESS_ID)
+ .then()
+ .statusCode(201)
+ .header("Location", not(emptyOrNullString()))
+ .body("id", not(emptyOrNullString()))
+ .extract()
+ .path("id");
+
+ given()
+ .accept(ContentType.JSON)
+ .when()
+ .get("/{processId}/{id}", PROCESS_ID, pid)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(pid))
+ .body("traveller.firstName", equalTo(traveller.getFirstName()))
+ .body("traveller.lastName", equalTo(traveller.getLastName()))
+ .body("traveller.email", equalTo(traveller.getEmail()))
+ .body("traveller.nationality", equalTo(traveller.getNationality()));
+
+ return pid;
+ }
+}
diff --git a/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/test/java/org/jbpm/userTask/jpa/it/UserTaskAttachmentsIT.java b/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/test/java/org/jbpm/userTask/jpa/it/UserTaskAttachmentsIT.java
new file mode 100644
index 00000000000..630e207040b
--- /dev/null
+++ b/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/test/java/org/jbpm/userTask/jpa/it/UserTaskAttachmentsIT.java
@@ -0,0 +1,212 @@
+/*
+ * 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.userTask.jpa.it;
+
+import java.net.URI;
+
+import org.acme.travels.Address;
+import org.acme.travels.Traveller;
+import org.junit.jupiter.api.Test;
+import org.kie.kogito.testcontainers.quarkus.PostgreSqlQuarkusTestResource;
+import org.kie.kogito.usertask.model.AttachmentInfo;
+
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+import io.restassured.http.ContentType;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.Matchers.emptyOrNullString;
+
+@QuarkusIntegrationTest
+@QuarkusTestResource(value = PostgreSqlQuarkusTestResource.class, restrictToAnnotatedClass = true)
+public class UserTaskAttachmentsIT extends BaseUserTaskIT {
+ public static final String USER_TASKS_INSTANCE_ATTACHMENTS_ENDPOINT = USER_TASKS_INSTANCE_ENDPOINT + "/attachments";
+ public static final String USER_TASKS_INSTANCE_ATTACHMENT = USER_TASKS_INSTANCE_ATTACHMENTS_ENDPOINT + "/{attachmentId}";
+
+ @Test
+ public void testUserTaskAttachments() {
+ Traveller traveller = new Traveller("John", "Doe", "john.doe@example.com", "American", new Address("main street", "Boston", "10005", "US"));
+
+ startProcessInstance(traveller);
+
+ String taskId = given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_ENDPOINT)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(1))
+ .extract()
+ .path("[0].id");
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ATTACHMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(0));
+
+ AttachmentInfo attachment1 = new AttachmentInfo(URI.create("http://localhost:8080/attachment_1.txt"), "Attachment 1");
+
+ String attachment1Id = addAndVerifyAttachment(taskId, attachment1);
+
+ AttachmentInfo attachment2 = new AttachmentInfo(URI.create("http://localhost:8080/attachment_2.txt"), "Attachment 2");
+
+ String attachment2Id = addAndVerifyAttachment(taskId, attachment2);
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ATTACHMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(2));
+
+ attachment1 = new AttachmentInfo(URI.create("http://localhost:8080/new_attachment_1.txt"), "NEW Attachment 1");
+
+ updateAndVerifyAttachment(taskId, attachment1Id, attachment1);
+
+ attachment2 = new AttachmentInfo(URI.create("http://localhost:8080/new_attachment_2.txt"), "NEW Attachment 2");
+
+ updateAndVerifyAttachment(taskId, attachment2Id, attachment2);
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ATTACHMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(2))
+ .body("[0].id", not(emptyOrNullString()))
+ .body("[0].content", equalTo(attachment1.getUri().toString()))
+ .body("[0].name", equalTo(attachment1.getName()))
+ .body("[0].updatedBy", not(emptyOrNullString()))
+ .body("[0].updatedAt", not(emptyOrNullString()))
+ .body("[1].id", not(emptyOrNullString()))
+ .body("[1].content", equalTo(attachment2.getUri().toString()))
+ .body("[1].name", equalTo(attachment2.getName()))
+ .body("[1].updatedBy", not(emptyOrNullString()))
+ .body("[1].updatedAt", not(emptyOrNullString()));
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .delete(USER_TASKS_INSTANCE_ATTACHMENT, taskId, attachment1Id)
+ .then()
+ .statusCode(200);
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ATTACHMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(1));
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .delete(USER_TASKS_INSTANCE_ATTACHMENT, taskId, attachment2Id)
+ .then()
+ .statusCode(200);
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ATTACHMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(0));
+ }
+
+ private String addAndVerifyAttachment(String taskId, AttachmentInfo attachment) {
+ String id = given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .body(attachment)
+ .post(USER_TASKS_INSTANCE_ATTACHMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("id", not(emptyOrNullString()))
+ .body("content", equalTo(attachment.getUri().toString()))
+ .body("name", equalTo(attachment.getName()))
+ .body("updatedBy", not(emptyOrNullString()))
+ .body("updatedAt", not(emptyOrNullString()))
+ .extract()
+ .path("id");
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ATTACHMENT, taskId, id)
+ .then()
+ .statusCode(200)
+ .body("id", not(emptyOrNullString()))
+ .body("content", equalTo(attachment.getUri().toString()))
+ .body("name", equalTo(attachment.getName()))
+ .body("updatedBy", not(emptyOrNullString()))
+ .body("updatedAt", not(emptyOrNullString()));
+
+ return id;
+ }
+
+ private void updateAndVerifyAttachment(String taskId, String attachmentId, AttachmentInfo attachment) {
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .body(attachment)
+ .put(USER_TASKS_INSTANCE_ATTACHMENT, taskId, attachmentId)
+ .then()
+ .statusCode(200)
+ .body("id", not(emptyOrNullString()))
+ .body("content", equalTo(attachment.getUri().toString()))
+ .body("name", equalTo(attachment.getName()))
+ .body("updatedBy", not(emptyOrNullString()))
+ .body("updatedAt", not(emptyOrNullString()))
+ .extract()
+ .path("id");
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ATTACHMENT, taskId, attachmentId)
+ .then()
+ .statusCode(200)
+ .body("id", not(emptyOrNullString()))
+ .body("content", equalTo(attachment.getUri().toString()))
+ .body("name", equalTo(attachment.getName()))
+ .body("updatedBy", not(emptyOrNullString()))
+ .body("updatedAt", not(emptyOrNullString()));
+ }
+}
diff --git a/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/test/java/org/jbpm/userTask/jpa/it/UserTaskCommentsIT.java b/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/test/java/org/jbpm/userTask/jpa/it/UserTaskCommentsIT.java
new file mode 100644
index 00000000000..638c7e30266
--- /dev/null
+++ b/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/test/java/org/jbpm/userTask/jpa/it/UserTaskCommentsIT.java
@@ -0,0 +1,203 @@
+/*
+ * 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.userTask.jpa.it;
+
+import org.acme.travels.Address;
+import org.acme.travels.Traveller;
+import org.junit.jupiter.api.Test;
+import org.kie.kogito.testcontainers.quarkus.PostgreSqlQuarkusTestResource;
+import org.kie.kogito.usertask.model.CommentInfo;
+
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+import io.restassured.http.ContentType;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.Matchers.emptyOrNullString;
+
+@QuarkusIntegrationTest
+@QuarkusTestResource(value = PostgreSqlQuarkusTestResource.class, restrictToAnnotatedClass = true)
+public class UserTaskCommentsIT extends BaseUserTaskIT {
+ public static final String USER_TASKS_INSTANCE_COMMENTS_ENDPOINT = USER_TASKS_INSTANCE_ENDPOINT + "/comments";
+ public static final String USER_TASKS_INSTANCE_COMMENT = USER_TASKS_INSTANCE_COMMENTS_ENDPOINT + "/{commentId}";
+
+ @Test
+ public void testUserTaskComments() {
+ Traveller traveller = new Traveller("John", "Doe", "john.doe@example.com", "American", new Address("main street", "Boston", "10005", "US"));
+
+ startProcessInstance(traveller);
+
+ String taskId = given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_ENDPOINT)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(1))
+ .extract()
+ .path("[0].id");
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_COMMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(0));
+
+ CommentInfo comment1 = new CommentInfo("This is my second comment.");
+
+ String comment1Id = addAndVerifyComment(taskId, comment1);
+
+ CommentInfo comment2 = new CommentInfo("This is my second comment.");
+
+ String comment2Id = addAndVerifyComment(taskId, comment2);
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .get(USER_TASKS_INSTANCE_COMMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(2));
+
+ comment1 = new CommentInfo("This is the first comment modified");
+
+ updateAndVerifyComment(taskId, comment1Id, comment1);
+
+ comment2 = new CommentInfo("This is the second comment modified");
+
+ updateAndVerifyComment(taskId, comment2Id, comment2);
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_COMMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(2))
+ .body("[0].id", not(emptyOrNullString()))
+ .body("[0].content", equalTo(comment1.getComment()))
+ .body("[0].updatedBy", not(emptyOrNullString()))
+ .body("[0].updatedAt", not(emptyOrNullString()))
+ .body("[1].id", not(emptyOrNullString()))
+ .body("[1].content", equalTo(comment2.getComment()))
+ .body("[1].updatedBy", not(emptyOrNullString()))
+ .body("[1].updatedAt", not(emptyOrNullString()));
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .delete(USER_TASKS_INSTANCE_COMMENT, taskId, comment1Id)
+ .then()
+ .statusCode(200);
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_COMMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(1));
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .delete(USER_TASKS_INSTANCE_COMMENT, taskId, comment2Id)
+ .then()
+ .statusCode(200);
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_COMMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(0));
+ }
+
+ private String addAndVerifyComment(String taskId, CommentInfo comment) {
+ String id = given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .body(comment)
+ .post(USER_TASKS_INSTANCE_COMMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("id", not(emptyOrNullString()))
+ .body("content", equalTo(comment.getComment()))
+ .body("updatedBy", not(emptyOrNullString()))
+ .body("updatedAt", not(emptyOrNullString()))
+ .extract()
+ .path("id");
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_COMMENT, taskId, id)
+ .then()
+ .statusCode(200)
+ .body("id", not(emptyOrNullString()))
+ .body("content", equalTo(comment.getComment()))
+ .body("updatedBy", not(emptyOrNullString()))
+ .body("updatedAt", not(emptyOrNullString()));
+
+ return id;
+ }
+
+ private void updateAndVerifyComment(String taskId, String commentId, CommentInfo comment) {
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .body(comment)
+ .put(USER_TASKS_INSTANCE_COMMENT, taskId, commentId)
+ .then()
+ .statusCode(200)
+ .body("id", not(emptyOrNullString()))
+ .body("content", equalTo(comment.getComment()))
+ .body("updatedBy", not(emptyOrNullString()))
+ .body("updatedAt", not(emptyOrNullString()))
+ .extract()
+ .path("id");
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_COMMENT, taskId, commentId)
+ .then()
+ .statusCode(200)
+ .body("id", not(emptyOrNullString()))
+ .body("content", equalTo(comment.getComment()))
+ .body("updatedBy", not(emptyOrNullString()))
+ .body("updatedAt", not(emptyOrNullString()));
+ }
+}
diff --git a/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/test/java/org/jbpm/userTask/jpa/it/UserTaskInputs.java b/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/test/java/org/jbpm/userTask/jpa/it/UserTaskInputs.java
new file mode 100644
index 00000000000..13084ed8399
--- /dev/null
+++ b/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/test/java/org/jbpm/userTask/jpa/it/UserTaskInputs.java
@@ -0,0 +1,107 @@
+/*
+ * 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.userTask.jpa.it;
+
+import java.util.Map;
+
+import org.acme.travels.Address;
+import org.acme.travels.Traveller;
+import org.junit.jupiter.api.Test;
+import org.kie.kogito.testcontainers.quarkus.PostgreSqlQuarkusTestResource;
+
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+import io.restassured.http.ContentType;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.CoreMatchers.*;
+
+@QuarkusIntegrationTest
+@QuarkusTestResource(value = PostgreSqlQuarkusTestResource.class, restrictToAnnotatedClass = true)
+public class UserTaskInputs extends BaseUserTaskIT {
+ public static final String USER_TASKS_INSTANCE_INPUTS_ENDPOINT = USER_TASKS_INSTANCE_ENDPOINT + "/inputs";
+
+ @Test
+ public void testUserTaskInputs() {
+ Traveller traveller = new Traveller("John", "Doe", "john.doe@example.com", "American", new Address("main street", "Boston", "10005", "US"));
+
+ String pid = startProcessInstance(traveller);
+
+ String taskId = given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_ENDPOINT)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(1))
+ .extract()
+ .path("[0].id");
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(taskId))
+ .body("status.name", equalTo("Reserved"))
+ .body("taskName", equalTo("firstLineApproval"))
+ .body("potentialUsers", hasItem("manager"))
+ .body("potentialGroups", hasItem("managers"))
+ .body("inputs.traveller.firstName", equalTo(traveller.getFirstName()))
+ .body("inputs.traveller.lastName", equalTo(traveller.getLastName()))
+ .body("inputs.traveller.email", equalTo(traveller.getEmail()))
+ .body("inputs.traveller.nationality", equalTo(traveller.getNationality()))
+ .body("metadata.ProcessType", equalTo("BPMN"))
+ .body("metadata.ProcessVersion", equalTo("1.0"))
+ .body("metadata.ProcessId", equalTo(PROCESS_ID))
+ .body("metadata.ProcessInstanceId", equalTo(pid))
+ .body("metadata.ProcessInstanceState", equalTo(1));
+
+ Traveller newTraveller = new Traveller("Ned", "Stark", "n.stark@winterfell.com", "Northern", new Address("main street", "Winterfell", "10005", "WF"));
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .body(Map.of("traveller", newTraveller))
+ .put(USER_TASKS_INSTANCE_INPUTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200);
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("inputs.traveller.firstName", equalTo(newTraveller.getFirstName()))
+ .body("inputs.traveller.lastName", equalTo(newTraveller.getLastName()))
+ .body("inputs.traveller.email", equalTo(newTraveller.getEmail()))
+ .body("inputs.traveller.nationality", equalTo(newTraveller.getNationality()))
+ .body("inputs.traveller.address.street", equalTo(newTraveller.getAddress().getStreet()))
+ .body("inputs.traveller.address.city", equalTo(newTraveller.getAddress().getCity()))
+ .body("inputs.traveller.address.zipCode", equalTo(newTraveller.getAddress().getZipCode()))
+ .body("inputs.traveller.address.country", equalTo(newTraveller.getAddress().getCountry()));
+ }
+}
diff --git a/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/test/java/org/jbpm/userTask/jpa/it/UserTaskLifeCycleIT.java b/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/test/java/org/jbpm/userTask/jpa/it/UserTaskLifeCycleIT.java
new file mode 100644
index 00000000000..b2c7b29e11d
--- /dev/null
+++ b/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/test/java/org/jbpm/userTask/jpa/it/UserTaskLifeCycleIT.java
@@ -0,0 +1,192 @@
+/*
+ * 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.userTask.jpa.it;
+
+import java.util.Map;
+
+import org.acme.travels.Address;
+import org.acme.travels.Traveller;
+import org.junit.jupiter.api.Test;
+import org.kie.kogito.testcontainers.quarkus.PostgreSqlQuarkusTestResource;
+import org.kie.kogito.usertask.model.TransitionInfo;
+
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+import io.restassured.http.ContentType;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.CoreMatchers.*;
+
+@QuarkusIntegrationTest
+@QuarkusTestResource(value = PostgreSqlQuarkusTestResource.class, restrictToAnnotatedClass = true)
+public class UserTaskLifeCycleIT extends BaseUserTaskIT {
+ public static final String USER_TASKS_INSTANCE_TRANSITION_ENDPOINT = USER_TASKS_INSTANCE_ENDPOINT + "/transition";
+
+ @Test
+ public void testUserTaskLifeCycle() {
+
+ Traveller traveller = new Traveller("John", "Doe", "john.doe@example.com", "American", new Address("main street", "Boston", "10005", "US"));
+
+ final String pid = startProcessInstance(traveller);
+
+ String taskId = given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_ENDPOINT)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(1))
+ .extract()
+ .path("[0].id");
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(taskId))
+ .body("status.name", equalTo("Reserved"))
+ .body("taskName", equalTo("firstLineApproval"))
+ .body("potentialUsers", hasItem("manager"))
+ .body("potentialGroups", hasItem("managers"))
+ .body("inputs.traveller.firstName", equalTo(traveller.getFirstName()))
+ .body("inputs.traveller.lastName", equalTo(traveller.getLastName()))
+ .body("inputs.traveller.email", equalTo(traveller.getEmail()))
+ .body("inputs.traveller.nationality", equalTo(traveller.getNationality()))
+ .body("metadata.ProcessType", equalTo("BPMN"))
+ .body("metadata.ProcessVersion", equalTo("1.0"))
+ .body("metadata.ProcessId", equalTo(PROCESS_ID))
+ .body("metadata.ProcessInstanceId", equalTo(pid))
+ .body("metadata.ProcessInstanceState", equalTo(1));
+
+ given()
+ .contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .body(new TransitionInfo("complete", Map.of("approved", true)))
+ .post(USER_TASKS_INSTANCE_TRANSITION_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(taskId))
+ .body("status.name", equalTo("Completed"))
+ .body("status.terminate", equalTo("COMPLETED"))
+ .body("outputs.approved", equalTo(true));
+
+ // Manager is excluded for the secondLineApproval Task, he shouldn't be allowed to see the task
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_ENDPOINT)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(0));
+
+ taskId = given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "john")
+ .queryParam("group", "managers")
+ .get(USER_TASKS_ENDPOINT)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(1))
+ .body("[0].id", not(taskId))
+ .extract()
+ .path("[0].id");
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "john")
+ .queryParam("group", "managers")
+ .get(USER_TASKS_INSTANCE_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(taskId))
+ .body("status.name", equalTo("Ready"))
+ .body("taskName", equalTo("secondLineApproval"))
+ .body("excludedUsers", hasItem("manager"))
+ .body("potentialGroups", hasItem("managers"))
+ .body("inputs.traveller.firstName", equalTo(traveller.getFirstName()))
+ .body("inputs.traveller.lastName", equalTo(traveller.getLastName()))
+ .body("inputs.traveller.email", equalTo(traveller.getEmail()))
+ .body("inputs.traveller.nationality", equalTo(traveller.getNationality()))
+ .body("metadata.ProcessType", equalTo("BPMN"))
+ .body("metadata.ProcessVersion", equalTo("1.0"))
+ .body("metadata.ProcessId", equalTo(PROCESS_ID))
+ .body("metadata.ProcessInstanceId", equalTo(pid))
+ .body("metadata.ProcessInstanceState", equalTo(1));
+
+ // Manager is excluded for the secondLineApproval Task, he shouldn't be able to work on the task
+ given()
+ .contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .body(new TransitionInfo("claim"))
+ .post(USER_TASKS_INSTANCE_TRANSITION_ENDPOINT, taskId)
+ .then()
+ .statusCode(500);
+
+ given()
+ .contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "john")
+ .queryParam("group", "managers")
+ .body(new TransitionInfo("claim"))
+ .post(USER_TASKS_INSTANCE_TRANSITION_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(taskId));
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "john")
+ .queryParam("group", "managers")
+ .get(USER_TASKS_INSTANCE_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(taskId))
+ .body("status.name", equalTo("Reserved"));
+
+ given()
+ .contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "john")
+ .queryParam("group", "managers")
+ .body(new TransitionInfo("complete", Map.of("approved", true)))
+ .post(USER_TASKS_INSTANCE_TRANSITION_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(taskId))
+ .body("status.name", equalTo("Completed"))
+ .body("status.terminate", equalTo("COMPLETED"))
+ .body("outputs.approved", equalTo(true));
+
+ given()
+ .accept(ContentType.JSON)
+ .when()
+ .get("/{processId}/{id}", PROCESS_ID, pid)
+ .then()
+ .statusCode(404);
+ }
+}
diff --git a/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/test/java/org/jbpm/userTask/jpa/it/UserTaskOutputsIT.java b/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/test/java/org/jbpm/userTask/jpa/it/UserTaskOutputsIT.java
new file mode 100644
index 00000000000..1192545ac61
--- /dev/null
+++ b/quarkus/integration-tests/integration-tests-quarkus-usertasks/src/test/java/org/jbpm/userTask/jpa/it/UserTaskOutputsIT.java
@@ -0,0 +1,109 @@
+/*
+ * 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.userTask.jpa.it;
+
+import java.util.Map;
+
+import org.acme.travels.Address;
+import org.acme.travels.Traveller;
+import org.junit.jupiter.api.Test;
+import org.kie.kogito.testcontainers.quarkus.PostgreSqlQuarkusTestResource;
+
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+import io.restassured.http.ContentType;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.CoreMatchers.*;
+
+@QuarkusIntegrationTest
+@QuarkusTestResource(value = PostgreSqlQuarkusTestResource.class, restrictToAnnotatedClass = true)
+public class UserTaskOutputsIT extends BaseUserTaskIT {
+ public static final String USER_TASKS_INSTANCE_OUTPUTS_ENDPOINT = USER_TASKS_INSTANCE_ENDPOINT + "/outputs";
+
+ @Test
+ public void testUserTaskOutputs() {
+ Traveller traveller = new Traveller("John", "Doe", "john.doe@example.com", "American", new Address("main street", "Boston", "10005", "US"));
+
+ String pid = startProcessInstance(traveller);
+
+ String taskId = given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_ENDPOINT)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(1))
+ .extract()
+ .path("[0].id");
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(taskId))
+ .body("status.name", equalTo("Reserved"))
+ .body("taskName", equalTo("firstLineApproval"))
+ .body("potentialUsers", hasItem("manager"))
+ .body("potentialGroups", hasItem("managers"))
+ .body("inputs.traveller.firstName", equalTo(traveller.getFirstName()))
+ .body("inputs.traveller.lastName", equalTo(traveller.getLastName()))
+ .body("inputs.traveller.email", equalTo(traveller.getEmail()))
+ .body("inputs.traveller.nationality", equalTo(traveller.getNationality()))
+ .body("metadata.ProcessType", equalTo("BPMN"))
+ .body("metadata.ProcessVersion", equalTo("1.0"))
+ .body("metadata.ProcessId", equalTo(PROCESS_ID))
+ .body("metadata.ProcessInstanceId", equalTo(pid))
+ .body("metadata.ProcessInstanceState", equalTo(1));
+
+ Traveller newTraveller = new Traveller("Ned", "Stark", "n.stark@winterfell.com", "Northern", new Address("main street", "Winterfell", "10005", "WF"));
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .body(Map.of("traveller", newTraveller, "approved", true))
+ .put(USER_TASKS_INSTANCE_OUTPUTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200);
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(taskId))
+ .body("outputs.approved", is(true))
+ .body("outputs.traveller.firstName", equalTo(newTraveller.getFirstName()))
+ .body("outputs.traveller.lastName", equalTo(newTraveller.getLastName()))
+ .body("outputs.traveller.email", equalTo(newTraveller.getEmail()))
+ .body("outputs.traveller.nationality", equalTo(newTraveller.getNationality()))
+ .body("outputs.traveller.address.street", equalTo(newTraveller.getAddress().getStreet()))
+ .body("outputs.traveller.address.city", equalTo(newTraveller.getAddress().getCity()))
+ .body("outputs.traveller.address.zipCode", equalTo(newTraveller.getAddress().getZipCode()))
+ .body("outputs.traveller.address.country", equalTo(newTraveller.getAddress().getCountry()));
+ }
+}
diff --git a/quarkus/integration-tests/pom.xml b/quarkus/integration-tests/pom.xml
index 1fb26d5ec8d..48ba322a8bd 100644
--- a/quarkus/integration-tests/pom.xml
+++ b/quarkus/integration-tests/pom.xml
@@ -48,6 +48,7 @@
integration-tests-quarkus-processes
integration-tests-quarkus-processes-reactive
integration-tests-quarkus-processes-persistence
+ integration-tests-quarkus-usertasks
integration-tests-quarkus-source-files
integration-tests-quarkus-gradle
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/pom.xml b/springboot/addons/jbpm-usertask-storage-jpa/pom.xml
new file mode 100644
index 00000000000..36bed41e5cc
--- /dev/null
+++ b/springboot/addons/jbpm-usertask-storage-jpa/pom.xml
@@ -0,0 +1,35 @@
+
+
+
+ kogito-addons-springboot-parent
+ org.kie
+ 999-SNAPSHOT
+
+ 4.0.0
+
+ org.jbpm
+ jbpm-addons-springboot-usertask-storage-jpa
+ jBPM :: Add-Ons :: Spring Boot :: User Task Storage JPA
+ jBPM Add-On Spring Boot User Task Storage JPA
+
+
+ UTF-8
+ org.jbpm.usertask.storage.jpa.springboot
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.jbpm
+ jbpm-addons-usertask-storage-jpa
+
+
+ org.kie
+ kie-addons-springboot-flyway
+
+
+
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/SpringBootJPAUserTaskInstances.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/SpringBootJPAUserTaskInstances.java
new file mode 100644
index 00000000000..aadef95e407
--- /dev/null
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/SpringBootJPAUserTaskInstances.java
@@ -0,0 +1,37 @@
+/*
+ * 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.usertask.jpa.springboot;
+
+import org.jbpm.usertask.jpa.JPAUserTaskInstances;
+import org.jbpm.usertask.jpa.mapper.UserTaskInstanceEntityMapper;
+import org.jbpm.usertask.jpa.repository.UserTaskInstanceRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional
+@Component
+public class SpringBootJPAUserTaskInstances extends JPAUserTaskInstances {
+
+ @Autowired
+ public SpringBootJPAUserTaskInstances(UserTaskInstanceRepository userTaskInstanceRepository, UserTaskInstanceEntityMapper userTaskInstanceEntityMapper) {
+ super(userTaskInstanceRepository, userTaskInstanceEntityMapper);
+ }
+}
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootAttachmentsEntityMapper.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootAttachmentsEntityMapper.java
new file mode 100644
index 00000000000..6375f27ec8e
--- /dev/null
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootAttachmentsEntityMapper.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.springboot.mapper;
+
+import org.jbpm.usertask.jpa.mapper.AttachmentsEntityMapper;
+import org.jbpm.usertask.jpa.repository.AttachmentRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SpringBootAttachmentsEntityMapper extends AttachmentsEntityMapper {
+
+ @Autowired
+ public SpringBootAttachmentsEntityMapper(AttachmentRepository attachmentRepository) {
+ super(attachmentRepository);
+ }
+}
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootCommentsEntityMapper.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootCommentsEntityMapper.java
new file mode 100644
index 00000000000..2a8c4cb8215
--- /dev/null
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootCommentsEntityMapper.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.springboot.mapper;
+
+import org.jbpm.usertask.jpa.mapper.CommentsEntityMapper;
+import org.jbpm.usertask.jpa.repository.CommentRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SpringBootCommentsEntityMapper extends CommentsEntityMapper {
+
+ @Autowired
+ public SpringBootCommentsEntityMapper(CommentRepository commentRepository) {
+ super(commentRepository);
+ }
+}
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootTaskInputsEntityMapper.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootTaskInputsEntityMapper.java
new file mode 100644
index 00000000000..c0520b805b3
--- /dev/null
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootTaskInputsEntityMapper.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.springboot.mapper;
+
+import org.jbpm.usertask.jpa.mapper.TaskInputsEntityMapper;
+import org.jbpm.usertask.jpa.repository.TaskInputRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SpringBootTaskInputsEntityMapper extends TaskInputsEntityMapper {
+
+ @Autowired
+ public SpringBootTaskInputsEntityMapper(TaskInputRepository repository) {
+ super(repository);
+ }
+}
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootTaskMetadataEntityMapper.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootTaskMetadataEntityMapper.java
new file mode 100644
index 00000000000..9d52d325084
--- /dev/null
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootTaskMetadataEntityMapper.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.springboot.mapper;
+
+import org.jbpm.usertask.jpa.mapper.TaskMetadataEntityMapper;
+import org.jbpm.usertask.jpa.repository.TaskMetadataRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SpringBootTaskMetadataEntityMapper extends TaskMetadataEntityMapper {
+
+ @Autowired
+ public SpringBootTaskMetadataEntityMapper(TaskMetadataRepository repository) {
+ super(repository);
+ }
+}
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootTaskOutputsEntityMapper.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootTaskOutputsEntityMapper.java
new file mode 100644
index 00000000000..83f9634990a
--- /dev/null
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootTaskOutputsEntityMapper.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.springboot.mapper;
+
+import org.jbpm.usertask.jpa.mapper.TaskOutputsEntityMapper;
+import org.jbpm.usertask.jpa.repository.TaskOutputRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SpringBootTaskOutputsEntityMapper extends TaskOutputsEntityMapper {
+
+ @Autowired
+ public SpringBootTaskOutputsEntityMapper(TaskOutputRepository repository) {
+ super(repository);
+ }
+}
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java
new file mode 100644
index 00000000000..6c753b1b6a0
--- /dev/null
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/mapper/SpringBootUserTaskInstanceEntityMapper.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.springboot.mapper;
+
+import org.jbpm.usertask.jpa.mapper.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SpringBootUserTaskInstanceEntityMapper extends UserTaskInstanceEntityMapper {
+
+ @Autowired
+ public SpringBootUserTaskInstanceEntityMapper(AttachmentsEntityMapper attachmentsMapper, CommentsEntityMapper commentsMapper, TaskMetadataEntityMapper taskMetadataEntityMapper,
+ TaskInputsEntityMapper taskInputsEntityMapper, TaskOutputsEntityMapper taskOutputsEntityMapper) {
+ super(attachmentsMapper, commentsMapper, taskMetadataEntityMapper, taskInputsEntityMapper, taskOutputsEntityMapper);
+ }
+}
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootAttachmentRepository.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootAttachmentRepository.java
new file mode 100644
index 00000000000..d9fad245177
--- /dev/null
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootAttachmentRepository.java
@@ -0,0 +1,36 @@
+/*
+ * 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.usertask.jpa.springboot.repository;
+
+import org.jbpm.usertask.jpa.repository.AttachmentRepository;
+import org.jbpm.usertask.jpa.repository.UserTaskJPAContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+@Transactional
+public class SpringBootAttachmentRepository extends AttachmentRepository {
+
+ @Autowired
+ public SpringBootAttachmentRepository(UserTaskJPAContext context) {
+ super(context);
+ }
+}
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootCommentRepository.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootCommentRepository.java
new file mode 100644
index 00000000000..6181e505e7e
--- /dev/null
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootCommentRepository.java
@@ -0,0 +1,36 @@
+/*
+ * 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.usertask.jpa.springboot.repository;
+
+import org.jbpm.usertask.jpa.repository.CommentRepository;
+import org.jbpm.usertask.jpa.repository.UserTaskJPAContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+@Transactional
+public class SpringBootCommentRepository extends CommentRepository {
+
+ @Autowired
+ public SpringBootCommentRepository(UserTaskJPAContext context) {
+ super(context);
+ }
+}
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootTaskInputEntityRepository.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootTaskInputEntityRepository.java
new file mode 100644
index 00000000000..468301e813f
--- /dev/null
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootTaskInputEntityRepository.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.springboot.repository;
+
+import org.jbpm.usertask.jpa.repository.TaskInputRepository;
+import org.jbpm.usertask.jpa.repository.UserTaskJPAContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SpringBootTaskInputEntityRepository extends TaskInputRepository {
+
+ @Autowired
+ public SpringBootTaskInputEntityRepository(UserTaskJPAContext context) {
+ super(context);
+ }
+
+}
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootTaskMetadataEntityRepository.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootTaskMetadataEntityRepository.java
new file mode 100644
index 00000000000..c5064422a82
--- /dev/null
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootTaskMetadataEntityRepository.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.springboot.repository;
+
+import org.jbpm.usertask.jpa.repository.TaskMetadataRepository;
+import org.jbpm.usertask.jpa.repository.UserTaskJPAContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SpringBootTaskMetadataEntityRepository extends TaskMetadataRepository {
+
+ @Autowired
+ public SpringBootTaskMetadataEntityRepository(UserTaskJPAContext context) {
+ super(context);
+ }
+
+}
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootTaskOutputEntityRepository.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootTaskOutputEntityRepository.java
new file mode 100644
index 00000000000..3eefcd20778
--- /dev/null
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootTaskOutputEntityRepository.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.springboot.repository;
+
+import org.jbpm.usertask.jpa.repository.TaskOutputRepository;
+import org.jbpm.usertask.jpa.repository.UserTaskJPAContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SpringBootTaskOutputEntityRepository extends TaskOutputRepository {
+
+ @Autowired
+ public SpringBootTaskOutputEntityRepository(UserTaskJPAContext context) {
+ super(context);
+ }
+
+}
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootUserTaskInstanceRepository.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootUserTaskInstanceRepository.java
new file mode 100644
index 00000000000..b84df9dad37
--- /dev/null
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootUserTaskInstanceRepository.java
@@ -0,0 +1,36 @@
+/*
+ * 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.usertask.jpa.springboot.repository;
+
+import org.jbpm.usertask.jpa.repository.UserTaskInstanceRepository;
+import org.jbpm.usertask.jpa.repository.UserTaskJPAContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+@Transactional
+public class SpringBootUserTaskInstanceRepository extends UserTaskInstanceRepository {
+
+ @Autowired
+ public SpringBootUserTaskInstanceRepository(UserTaskJPAContext context) {
+ super(context);
+ }
+}
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootUserTaskJPAContext.java b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootUserTaskJPAContext.java
new file mode 100644
index 00000000000..a208f7ece9c
--- /dev/null
+++ b/springboot/addons/jbpm-usertask-storage-jpa/src/main/java/org/jbpm/usertask/jpa/springboot/repository/SpringBootUserTaskJPAContext.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.jbpm.usertask.jpa.springboot.repository;
+
+import org.jbpm.usertask.jpa.repository.UserTaskJPAContext;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.PersistenceContext;
+
+@Component
+@Transactional
+public class SpringBootUserTaskJPAContext implements UserTaskJPAContext {
+
+ @PersistenceContext
+ private EntityManager em;
+
+ @Override
+ public EntityManager getEntityManager() {
+ return em;
+ }
+}
diff --git a/springboot/addons/jbpm-usertask-storage-jpa/src/main/resources/META-INF/beans.xml b/springboot/addons/jbpm-usertask-storage-jpa/src/main/resources/META-INF/beans.xml
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/springboot/addons/pom.xml b/springboot/addons/pom.xml
index d6221ca7a6b..3aee177ac61 100644
--- a/springboot/addons/pom.xml
+++ b/springboot/addons/pom.xml
@@ -51,6 +51,7 @@
kubernetes
flyway
persistence
+ jbpm-usertask-storage-jpa
diff --git a/springboot/integration-tests/integration-tests-springboot-usertasks-it/pom.xml b/springboot/integration-tests/integration-tests-springboot-usertasks-it/pom.xml
new file mode 100644
index 00000000000..d3a7645e5f9
--- /dev/null
+++ b/springboot/integration-tests/integration-tests-springboot-usertasks-it/pom.xml
@@ -0,0 +1,153 @@
+
+
+
+ org.kie.kogito
+ kogito-spring-boot-integration-tests
+ 999-SNAPSHOT
+
+
+ 4.0.0
+
+ integration-tests-springboot-usertasks-it
+ Kogito :: Integration Tests :: Spring Boot :: UserTasks
+
+
+ integration.tests.springboot.userTasks.it
+ false
+
+
+
+
+
+ org.kie.kogito
+ kogito-spring-boot-bom
+ ${project.version}
+ pom
+ import
+
+
+
+
+
+
+ org.jbpm
+ jbpm-spring-boot-starter
+
+
+ org.kie
+ kie-addons-springboot-persistence-jdbc
+
+
+ org.jbpm
+ jbpm-addons-springboot-usertask-storage-jpa
+
+
+
+
+ org.postgresql
+ postgresql
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+ org.kie
+ kie-addons-springboot-process-management
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ io.rest-assured
+ json-schema-validator
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+ org.kie.kogito
+ kogito-spring-boot-test-utils
+ test
+
+
+ org.awaitility
+ awaitility
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ maven-compiler-plugin
+ ${version.compiler.plugin}
+
+ ${maven.compiler.release}
+
+
+
+ pre-kogito-generate-model
+ process-resources
+
+ compile
+
+
+
+
+
+ org.kie.kogito
+ kogito-maven-plugin
+ ${project.version}
+
+
+ kogito-generate-model
+ process-resources
+
+ generateModel
+
+
+
+ kogito-process-model-classes
+ process-classes
+
+ process-model-classes
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/main/java/org/acme/travels/Address.java b/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/main/java/org/acme/travels/Address.java
new file mode 100644
index 00000000000..662dfa9b9f7
--- /dev/null
+++ b/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/main/java/org/acme/travels/Address.java
@@ -0,0 +1,76 @@
+/*
+ * 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.acme.travels;
+
+public class Address {
+
+ private String street;
+ private String city;
+ private String zipCode;
+ private String country;
+
+ public Address() {
+
+ }
+
+ public Address(String street, String city, String zipCode, String country) {
+ super();
+ this.street = street;
+ this.city = city;
+ this.zipCode = zipCode;
+ this.country = country;
+ }
+
+ public String getStreet() {
+ return street;
+ }
+
+ public void setStreet(String street) {
+ this.street = street;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public void setCity(String city) {
+ this.city = city;
+ }
+
+ public String getZipCode() {
+ return zipCode;
+ }
+
+ public void setZipCode(String zipCode) {
+ this.zipCode = zipCode;
+ }
+
+ public String getCountry() {
+ return country;
+ }
+
+ public void setCountry(String country) {
+ this.country = country;
+ }
+
+ @Override
+ public String toString() {
+ return "Address [street=" + street + ", city=" + city + ", zipCode=" + zipCode + ", country=" + country + "]";
+ }
+}
diff --git a/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/main/java/org/acme/travels/Traveller.java b/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/main/java/org/acme/travels/Traveller.java
new file mode 100644
index 00000000000..c24685803d2
--- /dev/null
+++ b/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/main/java/org/acme/travels/Traveller.java
@@ -0,0 +1,88 @@
+/*
+ * 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.acme.travels;
+
+public class Traveller {
+
+ private String firstName;
+ private String lastName;
+ private String email;
+ private String nationality;
+ private Address address;
+
+ public Traveller() {
+
+ }
+
+ public Traveller(String firstName, String lastName, String email, String nationality, Address address) {
+ super();
+ this.firstName = firstName;
+ this.lastName = lastName;
+ this.email = email;
+ this.nationality = nationality;
+ this.address = address;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getNationality() {
+ return nationality;
+ }
+
+ public void setNationality(String nationality) {
+ this.nationality = nationality;
+ }
+
+ public Address getAddress() {
+ return address;
+ }
+
+ public void setAddress(Address address) {
+ this.address = address;
+ }
+
+ @Override
+ public String toString() {
+ return "Traveller [firstName=" + firstName + ", lastName=" + lastName + ", email=" + email + ", nationality="
+ + nationality + ", address=" + address + "]";
+ }
+
+}
diff --git a/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/main/java/org/kie/kogito/it/KogitoSpringbootApplication.java b/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/main/java/org/kie/kogito/it/KogitoSpringbootApplication.java
new file mode 100644
index 00000000000..8cc2437b71d
--- /dev/null
+++ b/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/main/java/org/kie/kogito/it/KogitoSpringbootApplication.java
@@ -0,0 +1,30 @@
+/*
+ * 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.it;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication(scanBasePackages = { "org.kie.kogito.**", "org.acme.travels.**" })
+public class KogitoSpringbootApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(KogitoSpringbootApplication.class, args);
+ }
+}
diff --git a/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/main/resources/application.properties b/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/main/resources/application.properties
new file mode 100644
index 00000000000..0e459353843
--- /dev/null
+++ b/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/main/resources/application.properties
@@ -0,0 +1,23 @@
+#
+# 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.
+#
+
+kogito.persistence.type=jdbc
+
+kie.flyway.enabled=true
+spring.flyway.enabled=false
\ No newline at end of file
diff --git a/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/main/resources/approval.bpmn b/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/main/resources/approval.bpmn
new file mode 100644
index 00000000000..275d148f7ac
--- /dev/null
+++ b/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/main/resources/approval.bpmn
@@ -0,0 +1,304 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ _9EAFE6C1-69B4-4908-B764-EF3C4A55BEE3
+ _C13522F1-230A-4C26-B5A9-533A5D9FEE9D
+
+
+
+
+
+
+
+
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_TaskNameInputX
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_travellerInputX
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_SkippableInputX
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_GroupIdInputX
+
+
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_ActorIdOutputX
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_approvedOutputX
+
+
+
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_TaskNameInputX
+
+
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_TaskNameInputX
+
+
+
+ traveller
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_travellerInputX
+
+
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_SkippableInputX
+
+
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_SkippableInputX
+
+
+
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_GroupIdInputX
+
+
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_GroupIdInputX
+
+
+
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_ActorIdOutputX
+ approver
+
+
+ _8B62D3CA-5D03-4B2B-832B-126469288BB4_approvedOutputX
+ firstLineApproval
+
+
+
+ manager
+
+
+
+
+
+
+
+
+
+ _C13522F1-230A-4C26-B5A9-533A5D9FEE9D
+ _078F46FB-B7A1-4DBB-BE9A-75C7CB0CCD03
+
+
+
+
+
+
+
+
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_TaskNameInputX
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_ExcludedOwnerIdInputX
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_travellerInputX
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_SkippableInputX
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_GroupIdInputX
+
+
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_approvedOutputX
+
+
+
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_TaskNameInputX
+
+
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_TaskNameInputX
+
+
+
+ approver
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_ExcludedOwnerIdInputX
+
+
+ traveller
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_travellerInputX
+
+
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_SkippableInputX
+
+
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_SkippableInputX
+
+
+
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_GroupIdInputX
+
+
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_GroupIdInputX
+
+
+
+ _0DBFABE8-92B0-46E6-B52E-A9593AFA4371_approvedOutputX
+ secondLineApproval
+
+
+
+
+
+
+
+
+ _9EAFE6C1-69B4-4908-B764-EF3C4A55BEE3
+
+
+
+
+
+
+
+ _078F46FB-B7A1-4DBB-BE9A-75C7CB0CCD03
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ _F0jB8En5EeqlfsIhq1UCRQ
+ _F0jB8En5EeqlfsIhq1UCRQ
+
+
diff --git a/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/test/java/org/jbpm/userTask/jpa/it/BaseUserTaskIT.java b/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/test/java/org/jbpm/userTask/jpa/it/BaseUserTaskIT.java
new file mode 100644
index 00000000000..3d8f3075882
--- /dev/null
+++ b/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/test/java/org/jbpm/userTask/jpa/it/BaseUserTaskIT.java
@@ -0,0 +1,90 @@
+/*
+ * 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.userTask.jpa.it;
+
+import java.util.Map;
+
+import org.acme.travels.Traveller;
+import org.junit.jupiter.api.BeforeEach;
+import org.springframework.boot.test.web.server.LocalServerPort;
+
+import io.restassured.RestAssured;
+import io.restassured.http.ContentType;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.Matchers.emptyOrNullString;
+
+public abstract class BaseUserTaskIT {
+ public static final String PROCESS_ID = "approvals";
+ public static final String PROCESS_INSTANCE = "approvals/{id}";
+ public static final String USER_TASKS_ENDPOINT = "usertasks/instance";
+ public static final String USER_TASKS_INSTANCE_ENDPOINT = USER_TASKS_ENDPOINT + "/{taskId}";
+
+ @LocalServerPort
+ int httpPort;
+
+ static {
+ RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
+ }
+
+ @BeforeEach
+ void setPort() {
+ RestAssured.port = httpPort;
+ }
+
+ public String startProcessInstance(Traveller traveller) {
+ final String pid = given().contentType(ContentType.JSON)
+ .when()
+ .body(Map.of("traveller", traveller))
+ .post("/{processId}", PROCESS_ID)
+ .then()
+ .statusCode(201)
+ .header("Location", not(emptyOrNullString()))
+ .body("id", not(emptyOrNullString()))
+ .extract()
+ .path("id");
+
+ given()
+ .accept(ContentType.JSON)
+ .when()
+ .get("/{processId}/{id}", PROCESS_ID, pid)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(pid))
+ .body("traveller.firstName", equalTo(traveller.getFirstName()))
+ .body("traveller.lastName", equalTo(traveller.getLastName()))
+ .body("traveller.email", equalTo(traveller.getEmail()))
+ .body("traveller.nationality", equalTo(traveller.getNationality()));
+
+ return pid;
+ }
+
+ public void abortProcessInstance(String pid) {
+ given().contentType(ContentType.JSON)
+ .when()
+ .body(Map.of())
+ .delete(PROCESS_INSTANCE, pid)
+ .then()
+ .statusCode(200)
+ .body("id", not(emptyOrNullString()));
+ }
+}
diff --git a/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/test/java/org/jbpm/userTask/jpa/it/UserTaskAttachmentsIT.java b/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/test/java/org/jbpm/userTask/jpa/it/UserTaskAttachmentsIT.java
new file mode 100644
index 00000000000..60887b11ffb
--- /dev/null
+++ b/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/test/java/org/jbpm/userTask/jpa/it/UserTaskAttachmentsIT.java
@@ -0,0 +1,215 @@
+/*
+ * 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.userTask.jpa.it;
+
+import java.net.URI;
+
+import org.acme.travels.Address;
+import org.acme.travels.Traveller;
+import org.junit.jupiter.api.Test;
+import org.kie.kogito.it.KogitoSpringbootApplication;
+import org.kie.kogito.testcontainers.springboot.PostgreSqlSpringBootTestResource;
+import org.kie.kogito.usertask.model.AttachmentInfo;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ContextConfiguration;
+
+import io.restassured.http.ContentType;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.Matchers.emptyOrNullString;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = KogitoSpringbootApplication.class)
+@ContextConfiguration(initializers = PostgreSqlSpringBootTestResource.class)
+public class UserTaskAttachmentsIT extends BaseUserTaskIT {
+ public static final String USER_TASKS_INSTANCE_ATTACHMENTS_ENDPOINT = USER_TASKS_INSTANCE_ENDPOINT + "/attachments";
+ public static final String USER_TASKS_INSTANCE_ATTACHMENT = USER_TASKS_INSTANCE_ATTACHMENTS_ENDPOINT + "/{attachmentId}";
+
+ @Test
+ public void testUserTaskAttachments() {
+ Traveller traveller = new Traveller("John", "Doe", "john.doe@example.com", "American", new Address("main street", "Boston", "10005", "US"));
+
+ final String pid = startProcessInstance(traveller);
+
+ String taskId = given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_ENDPOINT)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(1))
+ .extract()
+ .path("[0].id");
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ATTACHMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(0));
+
+ AttachmentInfo attachment1 = new AttachmentInfo(URI.create("http://localhost:8080/attachment_1.txt"), "Attachment 1");
+
+ String attachment1Id = addAndVerifyAttachment(taskId, attachment1);
+
+ AttachmentInfo attachment2 = new AttachmentInfo(URI.create("http://localhost:8080/attachment_2.txt"), "Attachment 2");
+
+ String attachment2Id = addAndVerifyAttachment(taskId, attachment2);
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ATTACHMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(2));
+
+ attachment1 = new AttachmentInfo(URI.create("http://localhost:8080/new_attachment_1.txt"), "NEW Attachment 1");
+
+ updateAndVerifyAttachment(taskId, attachment1Id, attachment1);
+
+ attachment2 = new AttachmentInfo(URI.create("http://localhost:8080/new_attachment_2.txt"), "NEW Attachment 2");
+
+ updateAndVerifyAttachment(taskId, attachment2Id, attachment2);
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ATTACHMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(2))
+ .body("[0].id", not(emptyOrNullString()))
+ .body("[0].content", equalTo(attachment1.getUri().toString()))
+ .body("[0].name", equalTo(attachment1.getName()))
+ .body("[0].updatedBy", not(emptyOrNullString()))
+ .body("[0].updatedAt", not(emptyOrNullString()))
+ .body("[1].id", not(emptyOrNullString()))
+ .body("[1].content", equalTo(attachment2.getUri().toString()))
+ .body("[1].name", equalTo(attachment2.getName()))
+ .body("[1].updatedBy", not(emptyOrNullString()))
+ .body("[1].updatedAt", not(emptyOrNullString()));
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .delete(USER_TASKS_INSTANCE_ATTACHMENT, taskId, attachment1Id)
+ .then()
+ .statusCode(200);
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ATTACHMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(1));
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .delete(USER_TASKS_INSTANCE_ATTACHMENT, taskId, attachment2Id)
+ .then()
+ .statusCode(200);
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ATTACHMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(0));
+
+ abortProcessInstance(pid);
+ }
+
+ private String addAndVerifyAttachment(String taskId, AttachmentInfo attachment) {
+ String id = given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .body(attachment)
+ .post(USER_TASKS_INSTANCE_ATTACHMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("id", not(emptyOrNullString()))
+ .body("content", equalTo(attachment.getUri().toString()))
+ .body("name", equalTo(attachment.getName()))
+ .body("updatedBy", not(emptyOrNullString()))
+ .body("updatedAt", not(emptyOrNullString()))
+ .extract()
+ .path("id");
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ATTACHMENT, taskId, id)
+ .then()
+ .statusCode(200)
+ .body("id", not(emptyOrNullString()))
+ .body("content", equalTo(attachment.getUri().toString()))
+ .body("name", equalTo(attachment.getName()))
+ .body("updatedBy", not(emptyOrNullString()))
+ .body("updatedAt", not(emptyOrNullString()));
+
+ return id;
+ }
+
+ private void updateAndVerifyAttachment(String taskId, String attachmentId, AttachmentInfo attachment) {
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .body(attachment)
+ .put(USER_TASKS_INSTANCE_ATTACHMENT, taskId, attachmentId)
+ .then()
+ .statusCode(200)
+ .body("id", not(emptyOrNullString()))
+ .body("content", equalTo(attachment.getUri().toString()))
+ .body("name", equalTo(attachment.getName()))
+ .body("updatedBy", not(emptyOrNullString()))
+ .body("updatedAt", not(emptyOrNullString()))
+ .extract()
+ .path("id");
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ATTACHMENT, taskId, attachmentId)
+ .then()
+ .statusCode(200)
+ .body("id", not(emptyOrNullString()))
+ .body("content", equalTo(attachment.getUri().toString()))
+ .body("name", equalTo(attachment.getName()))
+ .body("updatedBy", not(emptyOrNullString()))
+ .body("updatedAt", not(emptyOrNullString()));
+ }
+}
diff --git a/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/test/java/org/jbpm/userTask/jpa/it/UserTaskCommentsIT.java b/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/test/java/org/jbpm/userTask/jpa/it/UserTaskCommentsIT.java
new file mode 100644
index 00000000000..07f7f91c4ac
--- /dev/null
+++ b/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/test/java/org/jbpm/userTask/jpa/it/UserTaskCommentsIT.java
@@ -0,0 +1,207 @@
+/*
+ * 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.userTask.jpa.it;
+
+import org.acme.travels.Address;
+import org.acme.travels.Traveller;
+import org.junit.jupiter.api.Test;
+import org.kie.kogito.it.KogitoSpringbootApplication;
+import org.kie.kogito.testcontainers.springboot.PostgreSqlSpringBootTestResource;
+import org.kie.kogito.usertask.model.CommentInfo;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ContextConfiguration;
+
+import io.restassured.http.ContentType;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.Matchers.emptyOrNullString;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = KogitoSpringbootApplication.class)
+@ContextConfiguration(initializers = PostgreSqlSpringBootTestResource.class)
+public class UserTaskCommentsIT extends BaseUserTaskIT {
+ public static final String USER_TASKS_INSTANCE_COMMENTS_ENDPOINT = USER_TASKS_INSTANCE_ENDPOINT + "/comments";
+ public static final String USER_TASKS_INSTANCE_COMMENT = USER_TASKS_INSTANCE_COMMENTS_ENDPOINT + "/{commentId}";
+
+ @Test
+ public void testUserTaskComments() {
+ Traveller traveller = new Traveller("John", "Doe", "john.doe@example.com", "American", new Address("main street", "Boston", "10005", "US"));
+
+ final String pid = startProcessInstance(traveller);
+
+ String taskId = given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_ENDPOINT)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(1))
+ .extract()
+ .path("[0].id");
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_COMMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(0));
+
+ CommentInfo comment1 = new CommentInfo("This is my second comment.");
+
+ String comment1Id = addAndVerifyComment(taskId, comment1);
+
+ CommentInfo comment2 = new CommentInfo("This is my second comment.");
+
+ String comment2Id = addAndVerifyComment(taskId, comment2);
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_COMMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(2));
+
+ comment1 = new CommentInfo("This is the first comment modified");
+
+ updateAndVerifyComment(taskId, comment1Id, comment1);
+
+ comment2 = new CommentInfo("This is the second comment modified");
+
+ updateAndVerifyComment(taskId, comment2Id, comment2);
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_COMMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(2))
+ .body("[0].id", not(emptyOrNullString()))
+ .body("[0].content", equalTo(comment1.getComment()))
+ .body("[0].updatedBy", not(emptyOrNullString()))
+ .body("[0].updatedAt", not(emptyOrNullString()))
+ .body("[1].id", not(emptyOrNullString()))
+ .body("[1].content", equalTo(comment2.getComment()))
+ .body("[1].updatedBy", not(emptyOrNullString()))
+ .body("[1].updatedAt", not(emptyOrNullString()));
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .delete(USER_TASKS_INSTANCE_COMMENT, taskId, comment1Id)
+ .then()
+ .statusCode(200);
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_COMMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(1));
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .delete(USER_TASKS_INSTANCE_COMMENT, taskId, comment2Id)
+ .then()
+ .statusCode(200);
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_COMMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(0));
+
+ abortProcessInstance(pid);
+ }
+
+ private String addAndVerifyComment(String taskId, CommentInfo comment) {
+ String id = given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .body(comment)
+ .post(USER_TASKS_INSTANCE_COMMENTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("id", not(emptyOrNullString()))
+ .body("content", equalTo(comment.getComment()))
+ .body("updatedBy", not(emptyOrNullString()))
+ .body("updatedAt", not(emptyOrNullString()))
+ .extract()
+ .path("id");
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_COMMENT, taskId, id)
+ .then()
+ .statusCode(200)
+ .body("id", not(emptyOrNullString()))
+ .body("content", equalTo(comment.getComment()))
+ .body("updatedBy", not(emptyOrNullString()))
+ .body("updatedAt", not(emptyOrNullString()));
+
+ return id;
+ }
+
+ private void updateAndVerifyComment(String taskId, String commentId, CommentInfo comment) {
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .body(comment)
+ .put(USER_TASKS_INSTANCE_COMMENT, taskId, commentId)
+ .then()
+ .statusCode(200)
+ .body("id", not(emptyOrNullString()))
+ .body("content", equalTo(comment.getComment()))
+ .body("updatedBy", not(emptyOrNullString()))
+ .body("updatedAt", not(emptyOrNullString()))
+ .extract()
+ .path("id");
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_COMMENT, taskId, commentId)
+ .then()
+ .statusCode(200)
+ .body("id", not(emptyOrNullString()))
+ .body("content", equalTo(comment.getComment()))
+ .body("updatedBy", not(emptyOrNullString()))
+ .body("updatedAt", not(emptyOrNullString()));
+ }
+}
diff --git a/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/test/java/org/jbpm/userTask/jpa/it/UserTaskInputs.java b/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/test/java/org/jbpm/userTask/jpa/it/UserTaskInputs.java
new file mode 100644
index 00000000000..0357f040233
--- /dev/null
+++ b/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/test/java/org/jbpm/userTask/jpa/it/UserTaskInputs.java
@@ -0,0 +1,110 @@
+/*
+ * 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.userTask.jpa.it;
+
+import java.util.Map;
+
+import org.acme.travels.Address;
+import org.acme.travels.Traveller;
+import org.junit.jupiter.api.Test;
+import org.kie.kogito.it.KogitoSpringbootApplication;
+import org.kie.kogito.testcontainers.springboot.PostgreSqlSpringBootTestResource;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ContextConfiguration;
+
+import io.restassured.http.ContentType;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.CoreMatchers.*;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = KogitoSpringbootApplication.class)
+@ContextConfiguration(initializers = PostgreSqlSpringBootTestResource.class)
+public class UserTaskInputs extends BaseUserTaskIT {
+ public static final String USER_TASKS_INSTANCE_INPUTS_ENDPOINT = USER_TASKS_INSTANCE_ENDPOINT + "/inputs";
+
+ @Test
+ public void testUserTaskInputs() {
+ Traveller traveller = new Traveller("John", "Doe", "john.doe@example.com", "American", new Address("main street", "Boston", "10005", "US"));
+
+ String pid = startProcessInstance(traveller);
+
+ String taskId = given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_ENDPOINT)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(1))
+ .extract()
+ .path("[0].id");
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(taskId))
+ .body("status.name", equalTo("Reserved"))
+ .body("taskName", equalTo("firstLineApproval"))
+ .body("potentialUsers", hasItem("manager"))
+ .body("potentialGroups", hasItem("managers"))
+ .body("inputs.traveller.firstName", equalTo(traveller.getFirstName()))
+ .body("inputs.traveller.lastName", equalTo(traveller.getLastName()))
+ .body("inputs.traveller.email", equalTo(traveller.getEmail()))
+ .body("inputs.traveller.nationality", equalTo(traveller.getNationality()))
+ .body("metadata.ProcessType", equalTo("BPMN"))
+ .body("metadata.ProcessVersion", equalTo("1.0"))
+ .body("metadata.ProcessId", equalTo(PROCESS_ID))
+ .body("metadata.ProcessInstanceId", equalTo(pid))
+ .body("metadata.ProcessInstanceState", equalTo(1));
+
+ Traveller newTraveller = new Traveller("Ned", "Stark", "n.stark@winterfell.com", "Northern", new Address("main street", "Winterfell", "10005", "WF"));
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .body(Map.of("traveller", newTraveller))
+ .put(USER_TASKS_INSTANCE_INPUTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200);
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("inputs.traveller.firstName", equalTo(newTraveller.getFirstName()))
+ .body("inputs.traveller.lastName", equalTo(newTraveller.getLastName()))
+ .body("inputs.traveller.email", equalTo(newTraveller.getEmail()))
+ .body("inputs.traveller.nationality", equalTo(newTraveller.getNationality()))
+ .body("inputs.traveller.address.street", equalTo(newTraveller.getAddress().getStreet()))
+ .body("inputs.traveller.address.city", equalTo(newTraveller.getAddress().getCity()))
+ .body("inputs.traveller.address.zipCode", equalTo(newTraveller.getAddress().getZipCode()))
+ .body("inputs.traveller.address.country", equalTo(newTraveller.getAddress().getCountry()));
+
+ abortProcessInstance(pid);
+ }
+}
diff --git a/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/test/java/org/jbpm/userTask/jpa/it/UserTaskLifeCycleIT.java b/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/test/java/org/jbpm/userTask/jpa/it/UserTaskLifeCycleIT.java
new file mode 100644
index 00000000000..0f04acf9565
--- /dev/null
+++ b/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/test/java/org/jbpm/userTask/jpa/it/UserTaskLifeCycleIT.java
@@ -0,0 +1,196 @@
+/*
+ * 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.userTask.jpa.it;
+
+import java.util.Map;
+
+import org.acme.travels.Address;
+import org.acme.travels.Traveller;
+import org.junit.jupiter.api.Test;
+import org.kie.kogito.it.KogitoSpringbootApplication;
+import org.kie.kogito.testcontainers.springboot.PostgreSqlSpringBootTestResource;
+import org.kie.kogito.usertask.model.TransitionInfo;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ContextConfiguration;
+
+import io.restassured.http.ContentType;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.CoreMatchers.*;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = KogitoSpringbootApplication.class)
+@ContextConfiguration(initializers = PostgreSqlSpringBootTestResource.class)
+public class UserTaskLifeCycleIT extends BaseUserTaskIT {
+ public static final String USER_TASKS_INSTANCE_TRANSITION_ENDPOINT = USER_TASKS_INSTANCE_ENDPOINT + "/transition";
+
+ @Test
+ public void testUserTaskLifeCycle() {
+
+ Traveller traveller = new Traveller("John", "Doe", "john.doe@example.com", "American", new Address("main street", "Boston", "10005", "US"));
+
+ final String pid = startProcessInstance(traveller);
+
+ String taskId = given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_ENDPOINT)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(1))
+ .extract()
+ .path("[0].id");
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(taskId))
+ .body("status.name", equalTo("Reserved"))
+ .body("taskName", equalTo("firstLineApproval"))
+ .body("potentialUsers", hasItem("manager"))
+ .body("potentialGroups", hasItem("managers"))
+ .body("inputs.traveller.firstName", equalTo(traveller.getFirstName()))
+ .body("inputs.traveller.lastName", equalTo(traveller.getLastName()))
+ .body("inputs.traveller.email", equalTo(traveller.getEmail()))
+ .body("inputs.traveller.nationality", equalTo(traveller.getNationality()))
+ .body("metadata.ProcessType", equalTo("BPMN"))
+ .body("metadata.ProcessVersion", equalTo("1.0"))
+ .body("metadata.ProcessId", equalTo(PROCESS_ID))
+ .body("metadata.ProcessInstanceId", equalTo(pid))
+ .body("metadata.ProcessInstanceState", equalTo(1));
+
+ given()
+ .contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .body(new TransitionInfo("complete", Map.of("approved", true)))
+ .post(USER_TASKS_INSTANCE_TRANSITION_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(taskId))
+ .body("status.name", equalTo("Completed"))
+ .body("status.terminate", equalTo("COMPLETED"))
+ .body("outputs.approved", equalTo(true));
+
+ // Manager is excluded for the secondLineApproval Task, he shouldn't be allowed to see the task
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_ENDPOINT)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(0));
+
+ taskId = given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "john")
+ .queryParam("group", "managers")
+ .get(USER_TASKS_ENDPOINT)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(1))
+ .body("[0].id", not(taskId))
+ .extract()
+ .path("[0].id");
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "john")
+ .queryParam("group", "managers")
+ .get(USER_TASKS_INSTANCE_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(taskId))
+ .body("status.name", equalTo("Ready"))
+ .body("taskName", equalTo("secondLineApproval"))
+ .body("excludedUsers", hasItem("manager"))
+ .body("potentialGroups", hasItem("managers"))
+ .body("inputs.traveller.firstName", equalTo(traveller.getFirstName()))
+ .body("inputs.traveller.lastName", equalTo(traveller.getLastName()))
+ .body("inputs.traveller.email", equalTo(traveller.getEmail()))
+ .body("inputs.traveller.nationality", equalTo(traveller.getNationality()))
+ .body("metadata.ProcessType", equalTo("BPMN"))
+ .body("metadata.ProcessVersion", equalTo("1.0"))
+ .body("metadata.ProcessId", equalTo(PROCESS_ID))
+ .body("metadata.ProcessInstanceId", equalTo(pid))
+ .body("metadata.ProcessInstanceState", equalTo(1));
+
+ // Manager is excluded for the secondLineApproval Task, he shouldn't be able to work on the task
+ given()
+ .contentType(ContentType.JSON)
+ .when()
+ .body(Map.of())
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .body(new TransitionInfo("claim"))
+ .post(USER_TASKS_INSTANCE_TRANSITION_ENDPOINT, taskId)
+ .then()
+ .statusCode(500);
+
+ given()
+ .contentType(ContentType.JSON)
+ .when()
+ .body(Map.of())
+ .queryParam("user", "john")
+ .queryParam("group", "managers")
+ .body(new TransitionInfo("claim"))
+ .post(USER_TASKS_INSTANCE_TRANSITION_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(taskId));
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "john")
+ .queryParam("group", "managers")
+ .get(USER_TASKS_INSTANCE_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(taskId))
+ .body("status.name", equalTo("Reserved"));
+
+ given()
+ .contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "john")
+ .queryParam("group", "managers")
+ .body(new TransitionInfo("complete", Map.of("approved", true)))
+ .post(USER_TASKS_INSTANCE_TRANSITION_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(taskId))
+ .body("status.name", equalTo("Completed"))
+ .body("status.terminate", equalTo("COMPLETED"))
+ .body("outputs.approved", equalTo(true));
+
+ given()
+ .accept(ContentType.JSON)
+ .when()
+ .get("/{processId}/{id}", PROCESS_ID, pid)
+ .then()
+ .statusCode(404);
+ }
+}
diff --git a/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/test/java/org/jbpm/userTask/jpa/it/UserTaskOutputsIT.java b/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/test/java/org/jbpm/userTask/jpa/it/UserTaskOutputsIT.java
new file mode 100644
index 00000000000..b89e19989a1
--- /dev/null
+++ b/springboot/integration-tests/integration-tests-springboot-usertasks-it/src/test/java/org/jbpm/userTask/jpa/it/UserTaskOutputsIT.java
@@ -0,0 +1,112 @@
+/*
+ * 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.userTask.jpa.it;
+
+import java.util.Map;
+
+import org.acme.travels.Address;
+import org.acme.travels.Traveller;
+import org.junit.jupiter.api.Test;
+import org.kie.kogito.it.KogitoSpringbootApplication;
+import org.kie.kogito.testcontainers.springboot.PostgreSqlSpringBootTestResource;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.ContextConfiguration;
+
+import io.restassured.http.ContentType;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.CoreMatchers.*;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = KogitoSpringbootApplication.class)
+@ContextConfiguration(initializers = PostgreSqlSpringBootTestResource.class)
+public class UserTaskOutputsIT extends BaseUserTaskIT {
+ public static final String USER_TASKS_INSTANCE_OUTPUTS_ENDPOINT = USER_TASKS_INSTANCE_ENDPOINT + "/outputs";
+
+ @Test
+ public void testUserTaskOutputs() {
+ Traveller traveller = new Traveller("John", "Doe", "john.doe@example.com", "American", new Address("main street", "Boston", "10005", "US"));
+
+ String pid = startProcessInstance(traveller);
+
+ String taskId = given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_ENDPOINT)
+ .then()
+ .statusCode(200)
+ .body("$.size()", is(1))
+ .extract()
+ .path("[0].id");
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(taskId))
+ .body("status.name", equalTo("Reserved"))
+ .body("taskName", equalTo("firstLineApproval"))
+ .body("potentialUsers", hasItem("manager"))
+ .body("potentialGroups", hasItem("managers"))
+ .body("inputs.traveller.firstName", equalTo(traveller.getFirstName()))
+ .body("inputs.traveller.lastName", equalTo(traveller.getLastName()))
+ .body("inputs.traveller.email", equalTo(traveller.getEmail()))
+ .body("inputs.traveller.nationality", equalTo(traveller.getNationality()))
+ .body("metadata.ProcessType", equalTo("BPMN"))
+ .body("metadata.ProcessVersion", equalTo("1.0"))
+ .body("metadata.ProcessId", equalTo(PROCESS_ID))
+ .body("metadata.ProcessInstanceId", equalTo(pid))
+ .body("metadata.ProcessInstanceState", equalTo(1));
+
+ Traveller newTraveller = new Traveller("Ned", "Stark", "n.stark@winterfell.com", "Northern", new Address("main street", "Winterfell", "10005", "WF"));
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .body(Map.of("traveller", newTraveller, "approved", true))
+ .put(USER_TASKS_INSTANCE_OUTPUTS_ENDPOINT, taskId)
+ .then()
+ .statusCode(200);
+
+ given().contentType(ContentType.JSON)
+ .when()
+ .queryParam("user", "manager")
+ .queryParam("group", "department-managers")
+ .get(USER_TASKS_INSTANCE_ENDPOINT, taskId)
+ .then()
+ .statusCode(200)
+ .body("id", equalTo(taskId))
+ .body("outputs.approved", is(true))
+ .body("outputs.traveller.firstName", equalTo(newTraveller.getFirstName()))
+ .body("outputs.traveller.lastName", equalTo(newTraveller.getLastName()))
+ .body("outputs.traveller.email", equalTo(newTraveller.getEmail()))
+ .body("outputs.traveller.nationality", equalTo(newTraveller.getNationality()))
+ .body("outputs.traveller.address.street", equalTo(newTraveller.getAddress().getStreet()))
+ .body("outputs.traveller.address.city", equalTo(newTraveller.getAddress().getCity()))
+ .body("outputs.traveller.address.zipCode", equalTo(newTraveller.getAddress().getZipCode()))
+ .body("outputs.traveller.address.country", equalTo(newTraveller.getAddress().getCountry()));
+
+ abortProcessInstance(pid);
+ }
+}
diff --git a/springboot/integration-tests/pom.xml b/springboot/integration-tests/pom.xml
index 4bf6bb95a02..4e76b109677 100644
--- a/springboot/integration-tests/pom.xml
+++ b/springboot/integration-tests/pom.xml
@@ -41,6 +41,7 @@
integration-tests-springboot-norest-it
integration-tests-springboot-processes-it
integration-tests-springboot-processes-persistence-it
+ integration-tests-springboot-usertasks-it