/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jbpm.services.task.assignment.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.jbpm.services.task.assignment.UserTaskLoad;
import org.jbpm.services.task.utils.ClassUtil;
import org.kie.api.task.TaskContext;
import org.kie.api.task.model.User;
import org.kie.internal.task.api.TaskPersistenceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
public class TotalCompletionTimeLoadCalculator extends AbstractLoadCalculator {
private static final Logger logger = LoggerFactory.getLogger(TotalCompletionTimeLoadCalculator.class);
private static final String IDENTIFIER = "TotalCompletionTime";
private static Long timeToLive;
private static Cache<String, Double> taskDurations;
private static String TASK_LIST_QUERY = ""
+ "select new org.jbpm.services.task.assignment.impl.TaskInfo(t.taskData.actualOwner.id, t.name, t.taskData.processId, t.taskData.deploymentId, count(t)) "
+ "from TaskImpl t "
+ "where t.taskData.actualOwner in (:owners) and t.taskData.status in ('Reserved', 'InProgress', 'Suspended') "
+ "group by t.taskData.actualOwner, t.name, t.taskData.processId, t.taskData.deploymentId";
private static String TASK_AVG_DURATION = ""
+ "select new org.jbpm.services.task.assignment.impl.TaskAverageDuration(avg(bts.duration),t.taskData.deploymentId,t.taskData.processId,bts.taskName) "
+ "from BAMTaskSummaryImpl bts left join TaskImpl t on (bts.taskId = t.id) "
+ "where (bts.duration is not null) and t.taskData.processId = :procid and t.taskData.deploymentId = :depid and t.name = :taskname "
+ "group by t.taskData.deploymentId, t.taskData.processId, t.name";
/**
* Initialize the cache
*/
static {
timeToLive = Long.valueOf(System.getProperty("org.jbpm.services.task.assignment.taskduration.timetolive","1800000")); // defaults to 30 minutes
taskDurations = CacheBuilder.newBuilder().expireAfterWrite(timeToLive, TimeUnit.MILLISECONDS).build();
}
public TotalCompletionTimeLoadCalculator() {
super(IDENTIFIER);
}
// Function to get the task key out of a TaskInfo
private Function<TaskInfo, String> getTaskKey = (ti) -> {
return ti.getProcessId()+"_"+ti.getDeploymentId()+"_"+ti.getTaskName();
};
/**
* Retrieves a list of tasks that the users are currently assigned to, and which
* are waiting to be worked on or are being worked on
* @param users The list of users that we are interested in
* @param context The TaskContext which is associated with the new task
* @return A list of TaskInfo objects
*/
private synchronized List<TaskInfo> getUserActiveTaskLists(List<User> users, TaskContext context) {
TaskPersistenceContext taskContext = ((org.jbpm.services.task.commands.TaskContext)context).getPersistenceContext();
Map<String, Object> params = new HashMap<>();
params.put("owners", users);
return taskContext.queryStringWithParametersInTransaction(TASK_LIST_QUERY, params, ClassUtil.<List<TaskInfo>>castClass(List.class));
}
@Override
public UserTaskLoad getUserTaskLoad(User user, TaskContext context) {
UserTaskLoad load = new UserTaskLoad(IDENTIFIER,user);
List<User> users = Arrays.asList(user);
List<TaskInfo> userTasks = getUserActiveTaskLists(users,context);
if (userTasks == null || userTasks.isEmpty()) {
load.setCalculatedLoad(new Double(0));
} else {
Double loadForUser = new Double(0.0);
for (TaskInfo ti: userTasks) {
loadForUser += getTaskDuration(ti,context);
}
load.setCalculatedLoad(loadForUser);
}
return load;
}
@Override
public Collection<UserTaskLoad> getUserTaskLoads(List<User> users, TaskContext context) {
Collection<UserTaskLoad> loads = new ArrayList<>();
List<TaskInfo> usersTasks = getUserActiveTaskLists(users,context);
// If there are no user tasks then everyone gets a score of 0 (zero)
if (usersTasks == null || usersTasks.isEmpty()) {
users.forEach(u -> {
loads.add(new UserTaskLoad(IDENTIFIER,u,new Double(0)));
});
} else {
users.forEach(u -> {
Double loadForUser = new Double(0.0);
//
// Get the list of tasks for the user
//
List<TaskInfo> tasksForUser = usersTasks.stream()
.filter(ut -> ut.getOwnerId().equals(u.getId()))
.collect(Collectors.toList());
//
// For each task, retrieve the average duration of the task and
// multiply that value by the number of instances that the user
// has assigned, that are not completed
//
for (TaskInfo ti: tasksForUser) {
loadForUser += getTaskDuration(ti,context) * ti.getCount();
}
UserTaskLoad load = new UserTaskLoad(IDENTIFIER,u,loadForUser);
logger.debug("User load: {}",load);
loads.add(load);
});
}
return loads;
}
/**
* Getter that returns the "time to live" for the cache containing
* task's average duration
* @return
*/
public static Long getTimeToLive() {
return timeToLive;
}
/**
* Calculates the average duration for a target task, using a query
* against the BAMTaskSummary table
* @param context Used to retrieve a PersistenceContext
* @param processId The identifier for the process definition containing the target task
* @param deploymentId The identifier for the deployment which contains the target task
* @param name The name of the target task
* @return
*/
private Double calculateAverageDuration(TaskContext context, String processId, String deploymentId, String name) {
Double avgDur = new Double(1);
TaskPersistenceContext taskContext = ((org.jbpm.services.task.commands.TaskContext)context).getPersistenceContext();
Map<String, Object> params = new HashMap<>();
params.put("procid",processId);
params.put("depid", deploymentId);
params.put("taskname", name);
List<TaskAverageDuration> durations = taskContext.queryStringWithParametersInTransaction(TASK_AVG_DURATION, params, ClassUtil.<List<TaskAverageDuration>>castClass(List.class));
if (durations != null && !durations.isEmpty()) {
avgDur = durations.get(0).getAverageDuration();
logger.debug("Retrieved duration is {}",avgDur);
}
return avgDur;
}
/**
* Attempts to retrieve the duration of the given task, from the
* taskDurations cache. If it is not available from the cache then
* it calls the {@code calculateAverageDuration} and puts the result
* into the cache
* @param taskInfo {@link TaskInfo} containing the processId, deploymentId and name of the task
* @param taskContext Context that was passed in from the assignment service
* @return The average duration for the task
*/
private synchronized Double getTaskDuration(TaskInfo taskInfo, TaskContext taskContext) {
Double duration = new Double(1);
String taskKey = getTaskKey.apply(taskInfo);
duration = taskDurations.getIfPresent(taskKey);
// If the duration isn't available from the cache then we have to get a calculated value
if (duration == null) {
duration = calculateAverageDuration(taskContext,taskInfo.getProcessId(),taskInfo.getDeploymentId(),taskInfo.getTaskName());
taskDurations.put(taskKey, duration);
logger.debug("Newly calculated duration of {} is {}",taskKey,taskDurations.asMap().get(taskKey));
}
return duration;
}
}