Skip to content

Commit

Permalink
Lock for TasksÄ
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesrdi committed Oct 17, 2023
1 parent a4c3acc commit f644ae8
Show file tree
Hide file tree
Showing 31 changed files with 671 additions and 216 deletions.
194 changes: 97 additions & 97 deletions common/taskana-common-data/src/main/resources/sql/sample-data/task.sql

Large diffs are not rendered by default.

214 changes: 107 additions & 107 deletions common/taskana-common-data/src/main/resources/sql/test-data/task.sql

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ CREATE TABLE TASK
CUSTOM_INT_6 INT NULL,
CUSTOM_INT_7 INT NULL,
CUSTOM_INT_8 INT NULL,
LOCK_EXPIRE TIMESTAMP NULL,
PRIMARY KEY (ID),
CONSTRAINT UC_EXTERNAL_ID UNIQUE (EXTERNAL_ID),
CONSTRAINT TASK_WB FOREIGN KEY (WORKBASKET_ID) REFERENCES WORKBASKET ON DELETE NO ACTION,
Expand Down Expand Up @@ -463,4 +464,4 @@ COMMIT WORK ;
CREATE INDEX IDX_TASK_ID_HISTORY_EVENT ON TASK_HISTORY_EVENT
(TASK_ID ASC)
ALLOW REVERSE SCANS COLLECT SAMPLED DETAILED STATISTICS;
COMMIT WORK ;
COMMIT WORK ;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- this script updates the TASKANA database schema from version 7.0.0 to version 7.2.0.
SET SCHEMA %schemaName%;

INSERT INTO TASKANA_SCHEMA_VERSION (ID, VERSION, CREATED)
VALUES (TASKANA_SCHEMA_VERSION_ID_SEQ.NEXTVAL, '7.2.0', CURRENT_TIMESTAMP);

ALTER TABLE TASK
ADD COLUMN LOCK_EXPIRE TIMESTAMP NULL;
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ CREATE TABLE TASK
CUSTOM_INT_6 INT NULL,
CUSTOM_INT_7 INT NULL,
CUSTOM_INT_8 INT NULL,
LOCK_EXPIRE TIMESTAMP NULL,
PRIMARY KEY (ID),
CONSTRAINT UC_EXTERNAL_ID UNIQUE (EXTERNAL_ID),
CONSTRAINT TASK_WB FOREIGN KEY (WORKBASKET_ID) REFERENCES WORKBASKET ON DELETE NO ACTION,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- this script updates the TASKANA database schema from version 7.0.0 to version 7.2.0.
SET SCHEMA %schemaName%;

INSERT INTO TASKANA_SCHEMA_VERSION (ID, VERSION, CREATED)
VALUES (nextval('TASKANA_SCHEMA_VERSION_ID_SEQ'), '7.2.0', CURRENT_TIMESTAMP);

ALTER TABLE TASK
ADD (
`LOCK_EXPIRE` TIMESTAMP NULL
)
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ CREATE TABLE TASK
CUSTOM_INT_6 NUMBER(10) NULL,
CUSTOM_INT_7 NUMBER(10) NULL,
CUSTOM_INT_8 NUMBER(10) NULL,
LOCK_EXPIRE TIMESTAMP NULL,
CONSTRAINT TASK_PKEY PRIMARY KEY (ID),
CONSTRAINT UC_EXTERNAL_ID UNIQUE (EXTERNAL_ID),
CONSTRAINT TASK_WB FOREIGN KEY (WORKBASKET_ID) REFERENCES WORKBASKET(ID),
Expand Down Expand Up @@ -460,4 +461,4 @@ CREATE INDEX IDX_OBJECT_REFERE_ACCESS_LIST ON OBJECT_REFERENCE
COMMIT WORK ;
CREATE INDEX IDX_TASK_ID_HISTORY_EVENT ON TASK_HISTORY_EVENT
(TASK_ID ASC);
COMMIT WORK ;
COMMIT WORK ;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- this script updates the TASKANA database schema from version 7.0.0 to version 7.2.0.
ALTER
SESSION SET CURRENT_SCHEMA = %schemaName%;

INSERT INTO TASKANA_SCHEMA_VERSION (ID, VERSION, CREATED)
VALUES (TASKANA_SCHEMA_VERSION_ID_SEQ.NEXTVAL, '7.2.0', CURRENT_TIMESTAMP);

ALTER TABLE TASK
ADD LOCK_EXPIRE TIMESTAMP NULL;
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ CREATE TABLE TASK
CUSTOM_INT_6 INT NULL,
CUSTOM_INT_7 INT NULL,
CUSTOM_INT_8 INT NULL,
LOCK_EXPIRE TIMESTAMP NULL,
PRIMARY KEY (ID),
CONSTRAINT UC_EXTERNAL_ID UNIQUE (EXTERNAL_ID),
CONSTRAINT TASK_WB FOREIGN KEY (WORKBASKET_ID) REFERENCES WORKBASKET ON DELETE NO ACTION,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- this script updates the TASKANA database schema from version 7.0.0 to version 7.2.0.

SET search_path = %schemaName%;

INSERT INTO TASKANA_SCHEMA_VERSION (ID, VERSION, CREATED)
VALUES (nextval('TASKANA_SCHEMA_VERSION_ID_SEQ'), '7.2.0', CURRENT_TIMESTAMP);

ALTER TABLE TASK
ADD COLUMN LOCK_EXPIRE TIMESTAMP NULL;
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ void should_CreateSimpleManualTask() throws Exception {
assertThat(createdTask.getPrimaryObjRef()).isEqualTo(defaultObjectReference);
assertThat(createdTask.getCreated()).isNotNull();
assertThat(createdTask.getModified()).isNotNull();
assertThat(createdTask.getLockExpire()).isNotNull();
assertThat(createdTask.getBusinessProcessId()).isNotNull();
assertThat(createdTask.getClaimed()).isNull();
assertThat(createdTask.getCompleted()).isNull();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package acceptance.task.lock;

import static org.assertj.core.api.Assertions.assertThat;
import static pro.taskana.testapi.DefaultTestEntities.defaultTestClassification;
import static pro.taskana.testapi.DefaultTestEntities.defaultTestObjectReference;
import static pro.taskana.testapi.DefaultTestEntities.defaultTestWorkbasket;

import java.util.List;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import pro.taskana.classification.api.ClassificationService;
import pro.taskana.classification.api.models.ClassificationSummary;
import pro.taskana.task.api.TaskService;
import pro.taskana.task.api.models.ObjectReference;
import pro.taskana.task.api.models.Task;
import pro.taskana.task.api.models.TaskSummary;
import pro.taskana.testapi.TaskanaInject;
import pro.taskana.testapi.TaskanaIntegrationTest;
import pro.taskana.testapi.builder.TaskBuilder;
import pro.taskana.testapi.builder.UserBuilder;
import pro.taskana.testapi.builder.WorkbasketAccessItemBuilder;
import pro.taskana.testapi.security.WithAccessId;
import pro.taskana.user.api.UserService;
import pro.taskana.workbasket.api.WorkbasketPermission;
import pro.taskana.workbasket.api.WorkbasketService;
import pro.taskana.workbasket.api.models.WorkbasketSummary;

@TaskanaIntegrationTest
class LockTaskAccTest {
@TaskanaInject TaskService taskService;
@TaskanaInject ClassificationService classificationService;
@TaskanaInject WorkbasketService workbasketService;
@TaskanaInject UserService userService;

ClassificationSummary defaultClassificationSummary;
WorkbasketSummary defaultWorkbasketSummary;
ObjectReference defaultObjectReference;

@WithAccessId(user = "admin")
@BeforeAll
void setup() throws Exception {
defaultClassificationSummary =
defaultTestClassification().buildAndStoreAsSummary(classificationService);
defaultWorkbasketSummary = defaultTestWorkbasket().buildAndStoreAsSummary(workbasketService);

WorkbasketAccessItemBuilder.newWorkbasketAccessItem()
.workbasketId(defaultWorkbasketSummary.getId())
.accessId("user-1-2")
.permission(WorkbasketPermission.OPEN)
.permission(WorkbasketPermission.READ)
.permission(WorkbasketPermission.READTASKS)
.permission(WorkbasketPermission.EDITTASKS)
.permission(WorkbasketPermission.APPEND)
.buildAndStore(workbasketService);

defaultObjectReference = defaultTestObjectReference().build();

UserBuilder.newUser()
.id("user-1-2")
.firstName("Max")
.lastName("Mustermann")
.longName("Long name of user-1-2")
.buildAndStore(userService);
}

@WithAccessId(user = "admin")
@Test
void should_SelectAndLockTasks_When_TasksUnlocked() throws Exception {
Task task1 =
createDefaultTaskWithLock(false).buildAndStore(taskService);

Task task2 =
createDefaultTaskWithLock(false).buildAndStore(taskService);

List<String> taskIdsToLock = List.of(task1.getId(), task2.getId());

List<String> lockedTaskIds = taskService.selectAndLock(taskIdsToLock);

assertThat(lockedTaskIds).containsExactlyInAnyOrder(task1.getId(),task2.getId());
List<TaskSummary> lockedTasks =
taskService
.createTaskQuery()
.idIn(taskIdsToLock.toArray(new String[0]))
.list();
for (TaskSummary taskSummary : lockedTasks) {
assertThat(taskSummary.isLocked()).isTrue();
}
}

@WithAccessId(user = "admin")
@Test
void should_OnlySelectAndLockUnlockedTasks_When_SomeTasksLocked() throws Exception {
Task task1 =
createDefaultTaskWithLock(false).buildAndStore(taskService);
Task task2 =
createDefaultTaskWithLock(true).buildAndStore(taskService);

List<String> taskIds = List.of(task1.getId(), task2.getId());
List<String> lockedTaskIds = taskService.selectAndLock(taskIds);

assertThat(lockedTaskIds).containsExactlyInAnyOrder(task1.getId());
List<TaskSummary> lockedTasks =
taskService
.createTaskQuery()
.idIn(lockedTaskIds.toArray(new String[0]))
.list();
for (TaskSummary taskSummary : lockedTasks) {
assertThat(taskSummary.isLocked()).isTrue();
}
}

@WithAccessId(user = "admin")
@Test
void should_SelectAndUnlockTasks_When_TasksLocked() throws Exception {
Task task1 =
createDefaultTaskWithLock(true).buildAndStore(taskService);
Task task2 =
createDefaultTaskWithLock(true).buildAndStore(taskService);

List<String> taskIdsToUnlock = List.of(task1.getId(), task2.getId());
List<String> unlockedTaskIds = taskService.selectAndUnlock(taskIdsToUnlock);

assertThat(unlockedTaskIds).containsExactlyInAnyOrder(task1.getId(), task2.getId());
List<TaskSummary> unlockedTasks =
taskService
.createTaskQuery()
.idIn(taskIdsToUnlock.toArray(new String[0]))
.list();
for (TaskSummary taskSummary : unlockedTasks) {
assertThat(taskSummary.isLocked()).isFalse();
}
}

@WithAccessId(user = "admin")
@Test
void should_OnlySelectAndUnlockLockedTasks_When_SomeTasksUnlocked() throws Exception {
Task task1 =
createDefaultTaskWithLock(false).buildAndStore(taskService);
Task task2 =
createDefaultTaskWithLock(true).buildAndStore(taskService);

List<String> taskIds = List.of(task1.getId(), task2.getId());
List<String> lockedTaskIds = taskService.selectAndLock(taskIds);

assertThat(lockedTaskIds).containsExactlyInAnyOrder(task1.getId());
List<TaskSummary> lockedTasks =
taskService
.createTaskQuery()
.idIn(lockedTaskIds.toArray(new String[0]))
.list();
for (TaskSummary taskSummary : lockedTasks) {
assertThat(taskSummary.isLocked()).isTrue();
}
}

private TaskBuilder createDefaultTaskWithLock(Boolean locked) {
return TaskBuilder.newTask()
.classificationSummary(defaultClassificationSummary)
.workbasketSummary(defaultWorkbasketSummary)
.primaryObjRef(defaultObjectReference)
.locked(locked);
}
}
35 changes: 35 additions & 0 deletions lib/taskana-core/src/main/java/pro/taskana/task/api/TaskQuery.java
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,41 @@ public interface TaskQuery extends BaseQuery<TaskSummary, TaskQueryColumnName> {
*/
TaskQuery orderByCompleted(SortDirection sortDirection);

// endregion
// region lock_expire
/**
* Add the time intervals within which the task lock expires to your query. For each time interval,
* the database query will search for tasks whose created timestamp is after or at the interval's
* begin and before or at the interval's end. If more than one interval is specified, the query
* will connect them with the OR keyword. If either begin or end of an interval are null, these
* values will not be specified in the query.
*
* @param lockExpireWithin - the TimeIntervals within which the task expires
* @return the query
*/
TaskQuery lockExpireWithin(TimeInterval... lockExpireWithin);

/**
* Exclude the time intervals within which the task lock does not expire from your query. For each time
* interval, the database query will search for tasks whose created timestamp is before the
* interval's begin and after the interval's end. If more than one interval is specified, the
* query will connect them with the OR keyword. If either begin or end of an interval are null,
* these values will not be specified in the query.
*
* @param lockExpireNotWithin - the TimeIntervals within which the task wasn't created
* @return the query
*/
TaskQuery lockExpireNotWithin(TimeInterval... lockExpireNotWithin);

/**
* This method sorts the query result according to the lock expire timestamp.
*
* @param sortDirection Determines whether the result is sorted in ascending or descending order.
* If sortDirection is null, the result is sorted in ascending order
* @return the query
*/
TaskQuery orderByLockExpire(SortDirection sortDirection);

// endregion
// region name

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public enum TaskQueryColumnName implements QueryColumnName {
CLAIMED("t.claimed"),
COMPLETED("t.completed"),
MODIFIED("t.modified"),
LOCK_EXPIRE("t.lock_expire"),
PLANNED("t.planned"),
RECEIVED("t.received"),
DUE("t.due"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ public interface TaskService {
* <li><b>{@linkplain Task#getState() state}</b> - {@linkplain TaskState#READY}
* <li><b>{@linkplain Task#isRead() isRead}</b> - false
* <li><b>{@linkplain Task#isTransferred() isTransferred}</b> - false
* <li><b>{@linkplain Task#getLockExpire()} () lockExpire}</b> - null
* </ul>
*
* @param taskToCreate the transient {@linkplain Task} to be inserted
Expand Down Expand Up @@ -1019,4 +1020,22 @@ ObjectReference newObjectReference(
* @return a {@linkplain TaskCommentQuery}
*/
TaskCommentQuery createTaskCommentQuery();

/**
* Locks and returns a list {@linkplain String} of task Ids. If a corresponding {@linkplain Task}
* to a task Id is already locked, the task will not be locked again and will just be skipped.
*
* @param taskIds a list of {@linkplain String} of task Ids to be locked
* @return a list of {@linkplain String} of task Ids that are locked
*/
List<String> selectAndLock(List<String> taskIds);

/**
* Unlocks and returns a list {@linkplain String} of task Ids. If a corresponding {@linkplain Task}
* to a task Id is already unlocked, the task will not be unlocked again and will just be skipped.
*
* @param taskIds a list of {@linkplain String} of task Ids to be unlocked
* @return a list of {@linkplain String} of task Ids that are unlocked
*/
List<String> selectAndUnlock(List<String> taskIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ public interface TaskSummary {
*/
Instant getModified();

/**
* Returns the time when the {@linkplain Task} lock expire.
*
* @return the lock expire Instant
*/
Instant getLockExpire();

/**
* Returns the time when the {@linkplain Task} is planned to be executed.
*
Expand Down
Loading

0 comments on commit f644ae8

Please sign in to comment.