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();
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*
|
||||
|
|
|
@ -6,6 +6,7 @@ import org.apache.ibatis.session.SqlSession;
|
|||
|
||||
import pro.taskana.TaskanaEngine;
|
||||
import pro.taskana.history.HistoryEventProducer;
|
||||
import pro.taskana.taskrouting.TaskRoutingProducer;
|
||||
|
||||
/**
|
||||
* FOR INTERNAL USE ONLY.
|
||||
|
@ -69,4 +70,11 @@ public interface InternalTaskanaEngine {
|
|||
*/
|
||||
HistoryEventProducer getHistoryEventProducer();
|
||||
|
||||
/**
|
||||
* Retrieve TaskRoutingProducer.
|
||||
*
|
||||
* @return the TaskRoutingProducer instance.
|
||||
*/
|
||||
TaskRoutingProducer getTaskRoutingProducer();
|
||||
|
||||
}
|
||||
|
|
|
@ -193,7 +193,13 @@ public class TaskServiceImpl implements TaskService {
|
|||
} else if (task.getWorkbasketKey() != null) {
|
||||
workbasket = workbasketService.getWorkbasket(task.getWorkbasketKey(), task.getDomain());
|
||||
} 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()) {
|
||||
|
@ -357,6 +363,13 @@ public class TaskServiceImpl implements TaskService {
|
|||
return new TaskQueryImpl(taskanaEngine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task newTask() {
|
||||
TaskImpl task = new TaskImpl();
|
||||
task.setCallbackState(CallbackState.NONE);
|
||||
return task;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task newTask(String workbasketId) {
|
||||
TaskImpl task = new TaskImpl();
|
||||
|
|
|
@ -49,6 +49,7 @@ import pro.taskana.mappings.TaskMonitorMapper;
|
|||
import pro.taskana.mappings.WorkbasketAccessMapper;
|
||||
import pro.taskana.mappings.WorkbasketMapper;
|
||||
import pro.taskana.security.CurrentUserContext;
|
||||
import pro.taskana.taskrouting.TaskRoutingProducer;
|
||||
|
||||
/**
|
||||
* This is the implementation of TaskanaEngine.
|
||||
|
@ -64,6 +65,7 @@ public class TaskanaEngineImpl implements TaskanaEngine {
|
|||
protected ConnectionManagementMode mode = ConnectionManagementMode.PARTICIPATE;
|
||||
protected java.sql.Connection connection = null;
|
||||
private HistoryEventProducer historyEventProducer;
|
||||
private TaskRoutingProducer taskRoutingProducer;
|
||||
private InternalTaskanaEngineImpl internalTaskanaEngineImpl;
|
||||
|
||||
protected TaskanaEngineImpl(TaskanaEngineConfiguration taskanaEngineConfiguration) {
|
||||
|
@ -71,6 +73,7 @@ public class TaskanaEngineImpl implements TaskanaEngine {
|
|||
createTransactionFactory(taskanaEngineConfiguration.getUseManagedTransactions());
|
||||
this.sessionManager = createSqlSessionManager();
|
||||
historyEventProducer = HistoryEventProducer.getInstance(taskanaEngineConfiguration);
|
||||
taskRoutingProducer = TaskRoutingProducer.getInstance(this);
|
||||
this.internalTaskanaEngineImpl = new InternalTaskanaEngineImpl();
|
||||
}
|
||||
|
||||
|
@ -385,5 +388,10 @@ public class TaskanaEngineImpl implements TaskanaEngine {
|
|||
return historyEventProducer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskRoutingProducer getTaskRoutingProducer() {
|
||||
return taskRoutingProducer;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,7 +103,7 @@ public class WorkbasketAccessItemQueryImpl implements WorkbasketAccessItemQuery
|
|||
public List<WorkbasketAccessItem> list() {
|
||||
LOGGER.debug("entry to list(), this = {}", this);
|
||||
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()) {
|
||||
LOGGER.debug("exit from list(). Returning {} resulting Objects: {} ", result.size(),
|
||||
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