TSK-972 initial commit - add task routing SPI to taskana
This commit is contained in:
parent
b0ee202402
commit
32c27e323c
|
@ -229,6 +229,18 @@ public interface TaskService {
|
||||||
*/
|
*/
|
||||||
TaskQuery createTaskQuery();
|
TaskQuery createTaskQuery();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a not persisted instance of {@link Task}.
|
||||||
|
* The returned task has no workbasket Id set. When createTask() is
|
||||||
|
* invoked for this task, TaskService will call the TaskRouting SPI to
|
||||||
|
* determine a workbasket for the task. If the TaskRouting API is not active,
|
||||||
|
* e.g. because no TaskRouter is registered, or the TaskRouter(s) don't find a workbasket,
|
||||||
|
* the task will not be persisted.
|
||||||
|
*
|
||||||
|
* @return an empty new Task
|
||||||
|
*/
|
||||||
|
Task newTask();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a not persisted instance of {@link Task}.
|
* Returns a not persisted instance of {@link Task}.
|
||||||
*
|
*
|
||||||
|
|
|
@ -6,6 +6,7 @@ import org.apache.ibatis.session.SqlSession;
|
||||||
|
|
||||||
import pro.taskana.TaskanaEngine;
|
import pro.taskana.TaskanaEngine;
|
||||||
import pro.taskana.history.HistoryEventProducer;
|
import pro.taskana.history.HistoryEventProducer;
|
||||||
|
import pro.taskana.taskrouting.TaskRoutingProducer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FOR INTERNAL USE ONLY.
|
* FOR INTERNAL USE ONLY.
|
||||||
|
@ -69,4 +70,11 @@ public interface InternalTaskanaEngine {
|
||||||
*/
|
*/
|
||||||
HistoryEventProducer getHistoryEventProducer();
|
HistoryEventProducer getHistoryEventProducer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve TaskRoutingProducer.
|
||||||
|
*
|
||||||
|
* @return the TaskRoutingProducer instance.
|
||||||
|
*/
|
||||||
|
TaskRoutingProducer getTaskRoutingProducer();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -193,7 +193,13 @@ public class TaskServiceImpl implements TaskService {
|
||||||
} else if (task.getWorkbasketKey() != null) {
|
} else if (task.getWorkbasketKey() != null) {
|
||||||
workbasket = workbasketService.getWorkbasket(task.getWorkbasketKey(), task.getDomain());
|
workbasket = workbasketService.getWorkbasket(task.getWorkbasketKey(), task.getDomain());
|
||||||
} else {
|
} else {
|
||||||
throw new InvalidArgumentException("Cannot create a task outside a workbasket");
|
String workbasketId = taskanaEngine.getTaskRoutingProducer().routeToWorkbasketId(task);
|
||||||
|
if (workbasketId != null) {
|
||||||
|
workbasket = workbasketService.getWorkbasket(workbasketId);
|
||||||
|
task.setWorkbasketSummary(workbasket.asSummary());
|
||||||
|
} else {
|
||||||
|
throw new InvalidArgumentException("Cannot create a task outside a workbasket");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (workbasket.isMarkedForDeletion()) {
|
if (workbasket.isMarkedForDeletion()) {
|
||||||
|
@ -357,6 +363,13 @@ public class TaskServiceImpl implements TaskService {
|
||||||
return new TaskQueryImpl(taskanaEngine);
|
return new TaskQueryImpl(taskanaEngine);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Task newTask() {
|
||||||
|
TaskImpl task = new TaskImpl();
|
||||||
|
task.setCallbackState(CallbackState.NONE);
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Task newTask(String workbasketId) {
|
public Task newTask(String workbasketId) {
|
||||||
TaskImpl task = new TaskImpl();
|
TaskImpl task = new TaskImpl();
|
||||||
|
|
|
@ -49,6 +49,7 @@ import pro.taskana.mappings.TaskMonitorMapper;
|
||||||
import pro.taskana.mappings.WorkbasketAccessMapper;
|
import pro.taskana.mappings.WorkbasketAccessMapper;
|
||||||
import pro.taskana.mappings.WorkbasketMapper;
|
import pro.taskana.mappings.WorkbasketMapper;
|
||||||
import pro.taskana.security.CurrentUserContext;
|
import pro.taskana.security.CurrentUserContext;
|
||||||
|
import pro.taskana.taskrouting.TaskRoutingProducer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the implementation of TaskanaEngine.
|
* This is the implementation of TaskanaEngine.
|
||||||
|
@ -64,6 +65,7 @@ public class TaskanaEngineImpl implements TaskanaEngine {
|
||||||
protected ConnectionManagementMode mode = ConnectionManagementMode.PARTICIPATE;
|
protected ConnectionManagementMode mode = ConnectionManagementMode.PARTICIPATE;
|
||||||
protected java.sql.Connection connection = null;
|
protected java.sql.Connection connection = null;
|
||||||
private HistoryEventProducer historyEventProducer;
|
private HistoryEventProducer historyEventProducer;
|
||||||
|
private TaskRoutingProducer taskRoutingProducer;
|
||||||
private InternalTaskanaEngineImpl internalTaskanaEngineImpl;
|
private InternalTaskanaEngineImpl internalTaskanaEngineImpl;
|
||||||
|
|
||||||
protected TaskanaEngineImpl(TaskanaEngineConfiguration taskanaEngineConfiguration) {
|
protected TaskanaEngineImpl(TaskanaEngineConfiguration taskanaEngineConfiguration) {
|
||||||
|
@ -71,6 +73,7 @@ public class TaskanaEngineImpl implements TaskanaEngine {
|
||||||
createTransactionFactory(taskanaEngineConfiguration.getUseManagedTransactions());
|
createTransactionFactory(taskanaEngineConfiguration.getUseManagedTransactions());
|
||||||
this.sessionManager = createSqlSessionManager();
|
this.sessionManager = createSqlSessionManager();
|
||||||
historyEventProducer = HistoryEventProducer.getInstance(taskanaEngineConfiguration);
|
historyEventProducer = HistoryEventProducer.getInstance(taskanaEngineConfiguration);
|
||||||
|
taskRoutingProducer = TaskRoutingProducer.getInstance(this);
|
||||||
this.internalTaskanaEngineImpl = new InternalTaskanaEngineImpl();
|
this.internalTaskanaEngineImpl = new InternalTaskanaEngineImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,5 +388,10 @@ public class TaskanaEngineImpl implements TaskanaEngine {
|
||||||
return historyEventProducer;
|
return historyEventProducer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TaskRoutingProducer getTaskRoutingProducer() {
|
||||||
|
return taskRoutingProducer;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,7 +103,7 @@ public class WorkbasketAccessItemQueryImpl implements WorkbasketAccessItemQuery
|
||||||
public List<WorkbasketAccessItem> list() {
|
public List<WorkbasketAccessItem> list() {
|
||||||
LOGGER.debug("entry to list(), this = {}", this);
|
LOGGER.debug("entry to list(), this = {}", this);
|
||||||
List<WorkbasketAccessItem> result = taskanaEngine.openAndReturnConnection(
|
List<WorkbasketAccessItem> result = taskanaEngine.openAndReturnConnection(
|
||||||
() -> new ArrayList<>(taskanaEngine.getSqlSession().selectList(LINK_TO_MAPPER, this)));
|
() -> new ArrayList<WorkbasketAccessItem>(taskanaEngine.getSqlSession().selectList(LINK_TO_MAPPER, this)));
|
||||||
if (LOGGER.isDebugEnabled()) {
|
if (LOGGER.isDebugEnabled()) {
|
||||||
LOGGER.debug("exit from list(). Returning {} resulting Objects: {} ", result.size(),
|
LOGGER.debug("exit from list(). Returning {} resulting Objects: {} ", result.size(),
|
||||||
LoggerUtils.listToString(result));
|
LoggerUtils.listToString(result));
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
package pro.taskana.taskrouting;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.ServiceLoader;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import pro.taskana.Task;
|
||||||
|
import pro.taskana.TaskanaEngine;
|
||||||
|
import pro.taskana.taskrouting.api.TaskRouter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads TaskRouter SPI implementation(s) and passes requests route tasks to workbaskets to the router(s).
|
||||||
|
*/
|
||||||
|
public final class TaskRoutingProducer {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(TaskRoutingProducer.class);
|
||||||
|
private static TaskRoutingProducer singleton;
|
||||||
|
private static boolean enabled = false;
|
||||||
|
private ServiceLoader<TaskRouter> serviceLoader;
|
||||||
|
private static List<TaskRouter> theTaskRouters = new ArrayList<>();
|
||||||
|
|
||||||
|
private TaskRoutingProducer(TaskanaEngine taskanaEngine) {
|
||||||
|
serviceLoader = ServiceLoader.load(TaskRouter.class);
|
||||||
|
for (TaskRouter router : serviceLoader) {
|
||||||
|
router.initialize(taskanaEngine);
|
||||||
|
theTaskRouters.add(router);
|
||||||
|
LOGGER.info("Registered TaskRouter provider: {}", router.getClass().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theTaskRouters.isEmpty()) {
|
||||||
|
LOGGER.info("No TaskRouter provider found. Running without Task Routing.");
|
||||||
|
} else {
|
||||||
|
enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized TaskRoutingProducer getInstance(TaskanaEngine taskanaEngine) {
|
||||||
|
if (singleton == null) {
|
||||||
|
singleton = new TaskRoutingProducer(taskanaEngine);
|
||||||
|
}
|
||||||
|
return singleton;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isTaskRoutingEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* routes tasks to Workbaskets.
|
||||||
|
* The task that is to be routed is passed to all registered TaskRouters. If they return no or more than one
|
||||||
|
* workbasketId, null is returned, otherwise we return the workbasketId that was returned from the TaskRouters.
|
||||||
|
* @param task the task for which a workbasketId is to be determined.
|
||||||
|
* @return the id of the workbasket in which the task is to be created.
|
||||||
|
*/
|
||||||
|
public String routeToWorkbasketId(Task task) {
|
||||||
|
LOGGER.debug("entry to routeToWorkbasket. TaskRouterr is enabled {}, task = {}", isTaskRoutingEnabled(), task);
|
||||||
|
String workbasketId = null;
|
||||||
|
if (isTaskRoutingEnabled()) {
|
||||||
|
// route to all task routers
|
||||||
|
// collect in a set to see whether different workbasket ids are returned
|
||||||
|
Set<String> workbasketIds = theTaskRouters.stream()
|
||||||
|
.map(rtr -> rtr.routeToWorkbasketId(task))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
if (workbasketIds.isEmpty()) {
|
||||||
|
LOGGER.error("No TaskRouter determined a workbasket for task {}.", task);
|
||||||
|
} else if (workbasketIds.size() > 1) {
|
||||||
|
LOGGER.error("The TaskRouters determined more than one workbasket for task{}", task);
|
||||||
|
} else {
|
||||||
|
workbasketId = workbasketIds.stream().findFirst().orElse(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOGGER.debug("exit from routeToWorkbasketId. Destination WorkbasketId = {}", workbasketId);
|
||||||
|
return workbasketId;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package pro.taskana.taskrouting.api;
|
||||||
|
|
||||||
|
import pro.taskana.Task;
|
||||||
|
import pro.taskana.TaskanaEngine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for TASKANA TaskRouter SPI.
|
||||||
|
*/
|
||||||
|
public interface TaskRouter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize TaskRouter service.
|
||||||
|
*
|
||||||
|
* @param taskanaEngine
|
||||||
|
* {@link TaskanaEngine} The Taskana engine for needed initialization.
|
||||||
|
*/
|
||||||
|
void initialize(TaskanaEngine taskanaEngine);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines a WorkbasketId for a given task.
|
||||||
|
*
|
||||||
|
* @param task
|
||||||
|
* {@link Task} The task for which a workbasket must be determined.
|
||||||
|
* @return the id of the workbasket in which the task is to be created.
|
||||||
|
*/
|
||||||
|
String routeToWorkbasketId(Task task);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package acceptance.taskrouting;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
|
import acceptance.AbstractAccTest;
|
||||||
|
import pro.taskana.Task;
|
||||||
|
import pro.taskana.TaskService;
|
||||||
|
import pro.taskana.exceptions.ClassificationNotFoundException;
|
||||||
|
import pro.taskana.exceptions.InvalidArgumentException;
|
||||||
|
import pro.taskana.exceptions.NotAuthorizedException;
|
||||||
|
import pro.taskana.exceptions.TaskAlreadyExistException;
|
||||||
|
import pro.taskana.exceptions.TaskNotFoundException;
|
||||||
|
import pro.taskana.exceptions.WorkbasketNotFoundException;
|
||||||
|
import pro.taskana.impl.TaskImpl;
|
||||||
|
import pro.taskana.security.JAASExtension;
|
||||||
|
import pro.taskana.security.WithAccessId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acceptance test for all "create task" scenarios.
|
||||||
|
*/
|
||||||
|
@ExtendWith(JAASExtension.class)
|
||||||
|
class TaskRoutingAccTest extends AbstractAccTest {
|
||||||
|
|
||||||
|
@WithAccessId(userName = "admin", groupNames = {"group_1"})
|
||||||
|
@Test
|
||||||
|
void testCreateTaskWithNullWorkbasket()
|
||||||
|
throws WorkbasketNotFoundException, ClassificationNotFoundException, NotAuthorizedException,
|
||||||
|
TaskAlreadyExistException, InvalidArgumentException, TaskNotFoundException {
|
||||||
|
TaskImpl createdTaskA = createTask("DOMAIN_A", "L12010");
|
||||||
|
assertEquals("WBI:100000000000000000000000000000000001", createdTaskA.getWorkbasketSummary().getId());
|
||||||
|
TaskImpl createdTaskB = createTask("DOMAIN_B", "T21001");
|
||||||
|
assertEquals("WBI:100000000000000000000000000000000011", createdTaskB.getWorkbasketSummary().getId());
|
||||||
|
Assertions.assertThrows(InvalidArgumentException.class, () -> createTask(null, "L12010"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private TaskImpl createTask(String domain, String classificationKey)
|
||||||
|
throws WorkbasketNotFoundException, ClassificationNotFoundException, NotAuthorizedException,
|
||||||
|
TaskAlreadyExistException, InvalidArgumentException {
|
||||||
|
TaskService taskService = taskanaEngine.getTaskService();
|
||||||
|
|
||||||
|
Task newTask = taskService.newTask(null, domain);
|
||||||
|
newTask.setClassificationKey(classificationKey);
|
||||||
|
|
||||||
|
newTask.setPrimaryObjRef(createObjectReference("COMPANY_A", "SYSTEM_A", "INSTANCE_A", "VNR", "1234567"));
|
||||||
|
TaskImpl createdTask = (TaskImpl) taskService.createTask(newTask);
|
||||||
|
return createdTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package acceptance.taskrouting;
|
||||||
|
|
||||||
|
import pro.taskana.Task;
|
||||||
|
import pro.taskana.TaskanaEngine;
|
||||||
|
import pro.taskana.taskrouting.api.TaskRouter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a sample implementation of TaskRouter.
|
||||||
|
*/
|
||||||
|
public class TestTaskRouterForDomainA implements TaskRouter {
|
||||||
|
|
||||||
|
TaskanaEngine theEngine;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(TaskanaEngine taskanaEngine) {
|
||||||
|
theEngine = taskanaEngine;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String routeToWorkbasketId(Task task) {
|
||||||
|
if ("DOMAIN_A".equals(task.getDomain())) {
|
||||||
|
return "WBI:100000000000000000000000000000000001";
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package acceptance.taskrouting;
|
||||||
|
|
||||||
|
import pro.taskana.Task;
|
||||||
|
import pro.taskana.TaskanaEngine;
|
||||||
|
import pro.taskana.taskrouting.api.TaskRouter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a sample implementation of TaskRouter.
|
||||||
|
*/
|
||||||
|
public class TestTaskRouterForDomainB implements TaskRouter {
|
||||||
|
|
||||||
|
TaskanaEngine theEngine;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(TaskanaEngine taskanaEngine) {
|
||||||
|
theEngine = taskanaEngine;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String routeToWorkbasketId(Task task) {
|
||||||
|
if ("DOMAIN_B".equals(task.getDomain())) {
|
||||||
|
return "WBI:100000000000000000000000000000000011";
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
acceptance.taskrouting.TestTaskRouterForDomainA
|
||||||
|
acceptance.taskrouting.TestTaskRouterForDomainB
|
Loading…
Reference in New Issue