TSK-1686: Introduced an SPI for the individual calculation of priorities
This commit is contained in:
parent
1c5456337b
commit
dc9e3a25ce
|
@ -37,6 +37,7 @@ import pro.taskana.spi.history.api.events.classification.ClassificationCreatedEv
|
|||
import pro.taskana.spi.history.api.events.classification.ClassificationDeletedEvent;
|
||||
import pro.taskana.spi.history.api.events.classification.ClassificationUpdatedEvent;
|
||||
import pro.taskana.spi.history.internal.HistoryEventManager;
|
||||
import pro.taskana.spi.priority.internal.PriorityServiceManager;
|
||||
import pro.taskana.task.api.models.TaskSummary;
|
||||
import pro.taskana.task.internal.TaskMapper;
|
||||
|
||||
|
@ -253,7 +254,10 @@ public class ClassificationServiceImpl implements ClassificationService {
|
|||
|
||||
this.checkExistenceOfParentClassification(oldClassification, classificationImpl);
|
||||
classificationMapper.update(classificationImpl);
|
||||
this.createJobIfPriorityOrServiceLevelHasChanged(oldClassification, classificationImpl);
|
||||
|
||||
if (!PriorityServiceManager.isPriorityServiceEnabled()) {
|
||||
this.createJobIfPriorityOrServiceLevelHasChanged(oldClassification, classificationImpl);
|
||||
}
|
||||
|
||||
if (HistoryEventManager.isHistoryEnabled()) {
|
||||
String details =
|
||||
|
|
|
@ -5,6 +5,7 @@ import org.apache.ibatis.session.SqlSession;
|
|||
|
||||
import pro.taskana.common.api.TaskanaEngine;
|
||||
import pro.taskana.spi.history.internal.HistoryEventManager;
|
||||
import pro.taskana.spi.priority.internal.PriorityServiceManager;
|
||||
import pro.taskana.spi.routing.internal.TaskRoutingManager;
|
||||
import pro.taskana.spi.task.internal.CreateTaskPreprocessorManager;
|
||||
|
||||
|
@ -97,4 +98,11 @@ public interface InternalTaskanaEngine {
|
|||
* @return the CreateTaskPreprocessorManager instance.
|
||||
*/
|
||||
CreateTaskPreprocessorManager getCreateTaskPreprocessorManager();
|
||||
|
||||
/**
|
||||
* Retrieves the {@linkplain PriorityServiceManager}.
|
||||
*
|
||||
* @return the {@linkplain PriorityServiceManager} instance
|
||||
*/
|
||||
PriorityServiceManager getPriorityServiceManager();
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ import pro.taskana.monitor.api.MonitorService;
|
|||
import pro.taskana.monitor.internal.MonitorMapper;
|
||||
import pro.taskana.monitor.internal.MonitorServiceImpl;
|
||||
import pro.taskana.spi.history.internal.HistoryEventManager;
|
||||
import pro.taskana.spi.priority.internal.PriorityServiceManager;
|
||||
import pro.taskana.spi.routing.internal.TaskRoutingManager;
|
||||
import pro.taskana.spi.task.internal.CreateTaskPreprocessorManager;
|
||||
import pro.taskana.task.api.TaskService;
|
||||
|
@ -76,6 +77,7 @@ public class TaskanaEngineImpl implements TaskanaEngine {
|
|||
private static final SessionStack SESSION_STACK = new SessionStack();
|
||||
private final TaskRoutingManager taskRoutingManager;
|
||||
private final CreateTaskPreprocessorManager createTaskPreprocessorManager;
|
||||
private final PriorityServiceManager priorityServiceManager;
|
||||
private final InternalTaskanaEngineImpl internalTaskanaEngineImpl;
|
||||
private final WorkingDaysToDaysConverter workingDaysToDaysConverter;
|
||||
private final HistoryEventManager historyEventManager;
|
||||
|
@ -106,6 +108,7 @@ public class TaskanaEngineImpl implements TaskanaEngine {
|
|||
// to provide a fully initialized TaskanaEngine instance during the SPI initialization!
|
||||
historyEventManager = HistoryEventManager.getInstance(this);
|
||||
taskRoutingManager = TaskRoutingManager.getInstance(this);
|
||||
priorityServiceManager = PriorityServiceManager.getInstance();
|
||||
}
|
||||
|
||||
public static TaskanaEngine createTaskanaEngine(
|
||||
|
@ -472,5 +475,10 @@ public class TaskanaEngineImpl implements TaskanaEngine {
|
|||
public CreateTaskPreprocessorManager getCreateTaskPreprocessorManager() {
|
||||
return createTaskPreprocessorManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PriorityServiceManager getPriorityServiceManager() {
|
||||
return priorityServiceManager;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package pro.taskana.spi.priority.api;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import pro.taskana.task.api.models.Task;
|
||||
import pro.taskana.task.api.models.TaskSummary;
|
||||
|
||||
/**
|
||||
* This SPI enables the computation of {@linkplain Task} priorities depending on individual
|
||||
* preferences.
|
||||
*/
|
||||
public interface PriorityServiceProvider {
|
||||
|
||||
/**
|
||||
* Calculates the {@linkplain Task#getPriority() priority} of a certain {@linkplain Task}.
|
||||
*
|
||||
* <p>The implemented method must calculate the {@linkplain Task#getPriority() priority}
|
||||
* efficiently. There can be a huge amount of {@linkplain Task Tasks} the SPI has to handle.
|
||||
*
|
||||
* @param taskSummary the {@linkplain TaskSummary} to compute the {@linkplain Task#getPriority()
|
||||
* priority} for
|
||||
* @return the computed {@linkplain Task#getPriority() priority}
|
||||
*/
|
||||
Optional<Integer> calculatePriority(TaskSummary taskSummary);
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package pro.taskana.spi.priority.internal;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import pro.taskana.common.api.exceptions.SystemException;
|
||||
import pro.taskana.common.internal.util.LogSanitizer;
|
||||
import pro.taskana.spi.priority.api.PriorityServiceProvider;
|
||||
import pro.taskana.task.api.models.TaskSummary;
|
||||
|
||||
public class PriorityServiceManager {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(PriorityServiceManager.class);
|
||||
private static PriorityServiceManager singleton;
|
||||
private final ServiceLoader<PriorityServiceProvider> serviceLoader;
|
||||
private boolean enabled = false;
|
||||
|
||||
private PriorityServiceManager() {
|
||||
serviceLoader = ServiceLoader.load(PriorityServiceProvider.class);
|
||||
for (PriorityServiceProvider priorityProvider : serviceLoader) {
|
||||
LOGGER.info("Registered PriorityServiceProvider: {}", priorityProvider.getClass().getName());
|
||||
enabled = true;
|
||||
}
|
||||
if (!enabled) {
|
||||
LOGGER.info("No PriorityServiceProvider found. Running without PriorityServiceProvider.");
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized PriorityServiceManager getInstance() {
|
||||
if (singleton == null) {
|
||||
singleton = new PriorityServiceManager();
|
||||
}
|
||||
return singleton;
|
||||
}
|
||||
|
||||
public static boolean isPriorityServiceEnabled() {
|
||||
return Objects.nonNull(singleton) && singleton.enabled;
|
||||
}
|
||||
|
||||
public Optional<Integer> calculatePriorityOfTask(TaskSummary task) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Sending task to PriorityServiceProviders: {}", task);
|
||||
}
|
||||
|
||||
// ServiceLoader.stream() is only available in Java11.
|
||||
List<Integer> priorities =
|
||||
StreamSupport.stream(serviceLoader.spliterator(), false)
|
||||
.map(provider -> getPriorityByProvider(task, provider))
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (priorities.size() <= 1) {
|
||||
return priorities.stream().findFirst();
|
||||
}
|
||||
|
||||
if (LOGGER.isErrorEnabled()) {
|
||||
LOGGER.error(
|
||||
"The PriorityServiceProviders determined more than one priority for Task {}.",
|
||||
LogSanitizer.stripLineBreakingChars(task));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<Integer> getPriorityByProvider(
|
||||
TaskSummary task, PriorityServiceProvider provider) {
|
||||
try {
|
||||
return provider.calculatePriority(task);
|
||||
} catch (Exception e) {
|
||||
throw new SystemException(
|
||||
String.format(
|
||||
"Caught exception while calculating priority of Task in provider %s.",
|
||||
provider.getClass().getName()),
|
||||
e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -47,6 +47,7 @@ import pro.taskana.spi.history.api.events.task.TaskCreatedEvent;
|
|||
import pro.taskana.spi.history.api.events.task.TaskTerminatedEvent;
|
||||
import pro.taskana.spi.history.api.events.task.TaskUpdatedEvent;
|
||||
import pro.taskana.spi.history.internal.HistoryEventManager;
|
||||
import pro.taskana.spi.priority.internal.PriorityServiceManager;
|
||||
import pro.taskana.spi.task.internal.CreateTaskPreprocessorManager;
|
||||
import pro.taskana.task.api.CallbackState;
|
||||
import pro.taskana.task.api.TaskCustomField;
|
||||
|
@ -99,6 +100,7 @@ public class TaskServiceImpl implements TaskService {
|
|||
private final AttachmentMapper attachmentMapper;
|
||||
private final HistoryEventManager historyEventManager;
|
||||
private final CreateTaskPreprocessorManager createTaskPreprocessorManager;
|
||||
private final PriorityServiceManager priorityServiceManager;
|
||||
|
||||
public TaskServiceImpl(
|
||||
InternalTaskanaEngine taskanaEngine,
|
||||
|
@ -112,6 +114,7 @@ public class TaskServiceImpl implements TaskService {
|
|||
this.classificationService = taskanaEngine.getEngine().getClassificationService();
|
||||
this.historyEventManager = taskanaEngine.getHistoryEventManager();
|
||||
this.createTaskPreprocessorManager = taskanaEngine.getCreateTaskPreprocessorManager();
|
||||
this.priorityServiceManager = taskanaEngine.getPriorityServiceManager();
|
||||
this.taskTransferrer = new TaskTransferrer(taskanaEngine, taskMapper, this);
|
||||
this.taskCommentService = new TaskCommentServiceImpl(taskanaEngine, taskCommentMapper, this);
|
||||
this.serviceLevelHandler =
|
||||
|
@ -222,6 +225,14 @@ public class TaskServiceImpl implements TaskService {
|
|||
ObjectReference.validate(task.getPrimaryObjRef(), "primary ObjectReference", "Task");
|
||||
standardSettingsOnTaskCreation(task, classification);
|
||||
setCallbackStateOnTaskCreation(task);
|
||||
|
||||
if (PriorityServiceManager.isPriorityServiceEnabled()) {
|
||||
Optional<Integer> newPriority = priorityServiceManager.calculatePriorityOfTask(task);
|
||||
if (newPriority.isPresent()) {
|
||||
task.setPriority(newPriority.get());
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
this.taskMapper.insert(task);
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
|
@ -419,6 +430,13 @@ public class TaskServiceImpl implements TaskService {
|
|||
|
||||
standardUpdateActions(oldTaskImpl, newTaskImpl);
|
||||
|
||||
if (PriorityServiceManager.isPriorityServiceEnabled()) {
|
||||
Optional<Integer> newPriority = priorityServiceManager.calculatePriorityOfTask(newTaskImpl);
|
||||
if (newPriority.isPresent()) {
|
||||
newTaskImpl.setPriority(newPriority.get());
|
||||
}
|
||||
}
|
||||
|
||||
taskMapper.update(newTaskImpl);
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
|
|
|
@ -71,7 +71,9 @@ class ArchitectureTest {
|
|||
"pro.taskana.spi.routing.api",
|
||||
"pro.taskana.spi.routing.internal",
|
||||
"pro.taskana.spi.task.api",
|
||||
"pro.taskana.spi.task.internal");
|
||||
"pro.taskana.spi.task.internal",
|
||||
"pro.taskana.spi.priority.api",
|
||||
"pro.taskana.spi.priority.internal");
|
||||
private static JavaClasses importedClasses;
|
||||
|
||||
@BeforeAll
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
package acceptance.priorityservice;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import acceptance.AbstractAccTest;
|
||||
import java.sql.Date;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.DynamicTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestFactory;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.function.ThrowingConsumer;
|
||||
|
||||
import pro.taskana.classification.api.ClassificationService;
|
||||
import pro.taskana.classification.api.models.Classification;
|
||||
import pro.taskana.common.api.ScheduledJob;
|
||||
import pro.taskana.common.internal.util.Pair;
|
||||
import pro.taskana.common.test.security.JaasExtension;
|
||||
import pro.taskana.common.test.security.WithAccessId;
|
||||
import pro.taskana.task.api.TaskCustomField;
|
||||
import pro.taskana.task.api.TaskService;
|
||||
import pro.taskana.task.api.models.ObjectReference;
|
||||
import pro.taskana.task.api.models.Task;
|
||||
|
||||
/** Acceptance test for all priority computation scenarios. */
|
||||
@Disabled("Until we enable the use of Test-SPI's in only specific tests")
|
||||
@ExtendWith(JaasExtension.class)
|
||||
class PriorityServiceAccTest extends AbstractAccTest {
|
||||
|
||||
private static final TaskService TASK_SERVICE = taskanaEngine.getTaskService();
|
||||
|
||||
@WithAccessId(user = "user-1-1")
|
||||
@TestFactory
|
||||
Stream<DynamicTest> should_SetThePriorityAccordingToTestProvider_When_CreatingTask() {
|
||||
List<Pair<String, Integer>> testCases = List.of(Pair.of("false", 1), Pair.of("true", 10));
|
||||
|
||||
ThrowingConsumer<Pair<String, Integer>> test =
|
||||
x -> {
|
||||
Task task = TASK_SERVICE.newTask("USER-1-1", "DOMAIN_A");
|
||||
task.setCustomAttribute(TaskCustomField.CUSTOM_6, x.getLeft());
|
||||
task.setClassificationKey("T2100");
|
||||
ObjectReference objectReference =
|
||||
createObjectReference("COMPANY_A", "SYSTEM_A", "INSTANCE_A", "VNR", "1234567");
|
||||
task.setPrimaryObjRef(objectReference);
|
||||
|
||||
Task createdTask = TASK_SERVICE.createTask(task);
|
||||
assertThat(createdTask.getPriority()).isEqualTo(x.getRight());
|
||||
};
|
||||
|
||||
return DynamicTest.stream(testCases.iterator(), x -> "entry in custom6: " + x.getLeft(), test);
|
||||
}
|
||||
|
||||
@WithAccessId(user = "user-1-1")
|
||||
@TestFactory
|
||||
Stream<DynamicTest> should_SetThePriorityAccordingToTestProvider_When_UpdatingTask()
|
||||
throws Exception {
|
||||
List<Pair<String, Integer>> testCases = List.of(Pair.of("false", 1), Pair.of("true", 10));
|
||||
Task task = TASK_SERVICE.getTask("TKI:000000000000000000000000000000000000");
|
||||
int daysSinceCreated =
|
||||
Math.toIntExact(
|
||||
TimeUnit.DAYS.convert(
|
||||
Date.from(Instant.now()).getTime() - Date.from(task.getCreated()).getTime(),
|
||||
TimeUnit.MILLISECONDS));
|
||||
|
||||
ThrowingConsumer<Pair<String, Integer>> test =
|
||||
x -> {
|
||||
task.setCustomAttribute(TaskCustomField.CUSTOM_6, x.getLeft());
|
||||
|
||||
Task updatedTask = TASK_SERVICE.updateTask(task);
|
||||
assertThat(updatedTask.getPriority()).isEqualTo(daysSinceCreated * x.getRight());
|
||||
};
|
||||
|
||||
return DynamicTest.stream(testCases.iterator(), x -> "entry in custom6: " + x.getLeft(), test);
|
||||
}
|
||||
|
||||
@WithAccessId(user = "admin")
|
||||
@Test
|
||||
void should_NotCreateClassificationChangedJob_When_PriorityProviderExisting() throws Exception {
|
||||
ClassificationService classificationService = taskanaEngine.getClassificationService();
|
||||
Classification classification =
|
||||
classificationService.getClassification("CLI:000000000000000000000000000000000001");
|
||||
classification.setPriority(10);
|
||||
|
||||
classificationService.updateClassification(classification);
|
||||
List<ScheduledJob> jobsToRun = getJobMapper().findJobsToRun(Instant.now());
|
||||
assertThat(jobsToRun).isEmpty();
|
||||
|
||||
classification.setServiceLevel("P4D");
|
||||
classificationService.updateClassification(classification);
|
||||
jobsToRun = getJobMapper().findJobsToRun(Instant.now());
|
||||
assertThat(jobsToRun).isEmpty();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package acceptance.priorityservice;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import pro.taskana.spi.priority.api.PriorityServiceProvider;
|
||||
import pro.taskana.task.api.TaskCustomField;
|
||||
import pro.taskana.task.api.models.TaskSummary;
|
||||
|
||||
public class TestPriorityServiceProvider implements PriorityServiceProvider {
|
||||
private static final int MULTIPLIER = 10;
|
||||
|
||||
@Override
|
||||
public Optional<Integer> calculatePriority(TaskSummary taskSummary) {
|
||||
long diffInMillies =
|
||||
Date.from(Instant.now()).getTime() - Date.from(taskSummary.getCreated()).getTime();
|
||||
long diffInDays = TimeUnit.DAYS.convert(diffInMillies, TimeUnit.MILLISECONDS);
|
||||
int priority = diffInDays >= 1 ? Math.toIntExact(diffInDays) : 1;
|
||||
|
||||
if (taskSummary.getCustomAttribute(TaskCustomField.CUSTOM_6) == "true") {
|
||||
priority *= MULTIPLIER;
|
||||
}
|
||||
return Optional.of(Integer.valueOf(priority));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue