use of org.kie.kogito.taskassigning.core.model.TaskAssigningSolution in project kogito-apps by kiegroup.
the class TaskAssigningService method onPlanningExecuted.
/**
* Invoked when the PlanningExecutor finalized the execution of a plan.
* Three main scenarios might happen:
* a) There are successful invocations and thus tasks were assigned, the corresponding "pinning changes" must be produced.
* Create and add them to the Solver.
*
* b) No "pinning changes" to execute there and no available events, retry with the planning items that failed.
*
* c) No "pinning changes" but there are available events, execute them.
*
* @param result a PlanningExecutionResult with results of the planning execution.
*/
synchronized void onPlanningExecuted(PlanningExecutionResult result) {
if (isNotOperative()) {
LOGGER.warn(SERVICE_INOPERATIVE_MESSAGE, context.getStatus());
return;
}
try {
LOGGER.debug("Planning was executed");
applyingPlanningExecutionResult.set(false);
TaskAssigningSolution solution = currentSolution.get();
Map<String, User> usersById = solution.getUserList().stream().collect(Collectors.toMap(User::getId, Function.identity()));
List<ProblemFactChange<TaskAssigningSolution>> pinningChanges = new ArrayList<>();
Task task;
User user;
boolean published;
for (PlanningExecutionResultItem resultItem : result.getItems()) {
task = resultItem.getItem().getTask();
published = !resultItem.hasError();
if (published) {
user = usersById.get(resultItem.getItem().getTargetUser());
pinningChanges.add(new AssignTaskProblemFactChange(new TaskAssignment(task), user));
}
context.setTaskPublished(task.getId(), published);
}
if (!pinningChanges.isEmpty()) {
LOGGER.debug("Pinning changes must be executed for the successful invocations: {}", pinningChanges.size());
pinningChanges.add(0, scoreDirector -> context.setCurrentChangeSetId(context.nextChangeSetId()));
applyingPlanningExecutionResult.set(true);
cancelScheduledImproveSolutionOnBackgroundTimer();
solverExecutor.addProblemFactChanges(pinningChanges);
} else if (!hasQueuedEvents()) {
List<PlanningItem> failingItems = result.getItems().stream().filter(PlanningExecutionResultItem::hasError).map(PlanningExecutionResultItem::getItem).collect(Collectors.toList());
LOGGER.debug("No new events to process, but some items failed: {}, we must retry", failingItems.size());
cancelScheduledImproveSolutionOnBackgroundTimer();
planningExecutor.start(failingItems, this::onPlanningExecuted);
} else {
LOGGER.debug("Some items failed but there are events to process, try to adjust the solution accordingly.");
resumeEvents();
}
} catch (Exception e) {
failFast(e);
}
}
use of org.kie.kogito.taskassigning.core.model.TaskAssigningSolution in project kogito-apps by kiegroup.
the class TaskAssigningService method processDataEvents.
/**
* Invoked when a set of events are received for processing.
* Three main scenarios might happen:
* a) A solution already exists and thus the proper problem fact changes are calculated and passed to the solver for
* execution. If there are no changes to apply, wait for more events.
*
* b) No solution exists. Instruct the solution data loader to read the users information and the solver will be
* started when this information is returned plus the information collected from the events.
*
* c) A solution improved on background event arrives and must be processed accordingly.
*
* @param events a list of events to process.
*/
synchronized void processDataEvents(List<DataEvent<?>> events) {
if (isNotOperative()) {
LOGGER.warn(SERVICE_INOPERATIVE_MESSAGE, context.getStatus());
return;
}
try {
List<TaskDataEvent> newTaskDataEvents = filterNewestTaskEventsInContext(context, events);
if (currentSolution.get() == null) {
List<TaskDataEvent> activeTaskEvents = newTaskDataEvents.stream().filter(IS_ACTIVE_TASK_EVENT).collect(Collectors.toList());
if (!activeTaskEvents.isEmpty()) {
// b) no solution exists, store the events and get the users from the external user service.
startingEvents = activeTaskEvents;
startingFromEvents.set(true);
loadSolutionData(false, true, config.getDataLoaderPageSize());
} else {
resumeEvents();
}
} else {
// a) a solution exists, calculate and apply the potential changes if any.
UserDataEvent userDataEvent = filterNewestUserEvent(events);
List<ProblemFactChange<TaskAssigningSolution>> changes = SolutionChangesBuilder.create().forSolution(currentSolution.get()).withContext(context).withUserServiceConnector(userServiceConnectorDelegate).withProcessors(processorRegistry).fromTasksData(fromTaskDataEvents(newTaskDataEvents)).fromUserDataEvent(userDataEvent).build();
if (!changes.isEmpty()) {
LOGGER.debug("processDataEvents - there are changes: {} to apply", changes.size());
cancelScheduledImproveSolutionOnBackgroundTimer();
solverExecutor.addProblemFactChanges(changes);
} else {
// c) check if an event for the improve solution on background period has arrived and a better
// solution was produced
SolutionUpdatedOnBackgroundDataEvent solutionImprovedOnBackgroundEvent = filterNewestSolutionUpdatedOnBackgroundEvent(events);
TaskAssigningSolution currentLastBestSolution = lastBestSolution.get();
if (solutionImprovedOnBackgroundEvent != null && hasToApplyImprovedOnBackgroundSolution(solutionImprovedOnBackgroundEvent, currentLastBestSolution)) {
// a better solution was produced during the improveSolutionOnBackgroundDuration period
LOGGER.debug("processDataEvents - apply the improved on background solution: {}", currentLastBestSolution);
executeSolutionChange(currentLastBestSolution);
} else {
executePlanOrResumeEvents(currentSolution.get());
}
}
}
} catch (Exception e) {
failFast(e);
}
}
use of org.kie.kogito.taskassigning.core.model.TaskAssigningSolution in project kogito-apps by kiegroup.
the class TaskAssigningService method onSolutionDataLoad.
/**
* Invoked by the SolutionDataLoader when the data for initializing the solution has been loaded successfully.
* Two main scenarios might happen:
* a) The service is starting and thus the initial solution load is attempted. If there are available tasks, the
* solver will be started, first solution will arrive, etc.
*
* b) No tasks where available at the time of service initialization and thus no solution to start the solver.
* At a later point in time events arrived and the solution can be started with the information coming for them plus
* the user information loaded by the solution data loader.
*
* @param result contains the requested data for creating the initial solution.
*/
synchronized void onSolutionDataLoad(SolutionDataLoader.Result result) {
if (isNotOperative()) {
LOGGER.warn(SERVICE_INOPERATIVE_MESSAGE, context.getStatus());
return;
}
try {
LOGGER.debug("Solution data loading has finished, startingFromEvents: {}, includeTasks: {}" + ", includeUsers: {}, tasks: {}, users: {}", startingFromEvents, !startingFromEvents.get(), true, result.getTasks().size(), result.getUsers().size());
context.setStatus(ServiceStatus.READY);
TaskAssigningSolution solution;
List<TaskAssignment> taskAssignments;
if (startingFromEvents.get()) {
// data loader has responded with the users.
if (hasQueuedEvents()) {
// incorporate the events that could have been arrived in the middle while the users were being loaded.
List<TaskDataEvent> newEvents = filterNewestTaskEventsInContext(context, pollEvents());
startingEvents = combineAndFilerNewestActiveTaskEvents(startingEvents, newEvents);
}
solution = SolutionBuilder.newBuilder().withTasks(fromTaskDataEvents(startingEvents)).withUsers(result.getUsers()).withProcessors(processorRegistry).build();
startingFromEvents.set(false);
startingEvents = null;
} else {
// a) normal initialization procedure after getting the tasks and users from the solution data loader
solution = SolutionBuilder.newBuilder().withTasks(result.getTasks()).withUsers(result.getUsers()).withProcessors(processorRegistry).build();
}
// if the solution has non dummy tasks the solver can be started.
taskAssignments = filterNonDummyAssignments(solution.getTaskAssignmentList());
if (!taskAssignments.isEmpty()) {
taskAssignments.forEach(taskAssignment -> {
context.setTaskPublished(taskAssignment.getId(), taskAssignment.isPinned());
context.setTaskLastEventTime(taskAssignment.getId(), taskAssignment.getTask().getLastUpdate());
});
solverExecutor.start(solution);
userServiceAdapter.start();
} else {
resumeEvents();
}
} catch (Exception e) {
failFast(e);
}
}
use of org.kie.kogito.taskassigning.core.model.TaskAssigningSolution in project kogito-apps by kiegroup.
the class TaskAssigningServiceTest method prepareStartAndSetInitialSolution.
private void prepareStartAndSetInitialSolution(SolutionDataLoader.Result result) throws Exception {
prepareStart();
solutionDataLoaderInitialExecution.complete(result);
verify(taskAssigningService).onSolutionDataLoad(result);
verify(solverExecutor).start(solutionCaptor.capture());
TaskAssigningSolution initialSolution = solutionCaptor.getValue();
BestSolutionChangedEvent<TaskAssigningSolution> solutionChangedEvent = mockEvent(initialSolution, true, true);
solverListenerCaptor.getValue().bestSolutionChanged(solutionChangedEvent);
verify(managedExecutor).runAsync(managedExecutorCaptor.capture());
managedExecutorCaptor.getValue().run();
verify(serviceEventConsumer).resume();
}
use of org.kie.kogito.taskassigning.core.model.TaskAssigningSolution in project kogito-apps by kiegroup.
the class TaskAssigningServiceTest method onSolutionChangeWithPlanningItems.
@Test
void onSolutionChangeWithPlanningItems() throws Exception {
prepareStart();
TaskAssigningSolution solution = buildSolution();
context.setTaskPublished(TASK_1_ID, false);
context.setTaskPublished(TASK_2_ID, true);
context.setTaskPublished(TASK_3_ID, false);
context.setTaskPublished(TASK_4_ID, true);
taskAssigningService.onBestSolutionChange(mockEvent(solution, true, true));
verify(managedExecutor).runAsync(managedExecutorCaptor.capture());
managedExecutorCaptor.getValue().run();
verify(planningExecutor).start(planningCaptor.capture(), any());
List<PlanningItem> planningItems = planningCaptor.getValue();
assertThat(planningItems).isNotNull().hasSize(2);
}
Aggregations