/* * 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.strategy; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.jbpm.services.task.assignment.impl.AssignmentImpl; import org.jbpm.services.task.utils.ClassUtil; import org.kie.api.runtime.EnvironmentName; import org.kie.api.task.TaskContext; import org.kie.api.task.model.Group; import org.kie.api.task.model.OrganizationalEntity; import org.kie.api.task.model.Task; import org.kie.api.task.model.User; import org.kie.internal.task.api.TaskPersistenceContext; import org.kie.internal.task.api.UserInfo; import org.kie.internal.task.api.assignment.Assignment; import org.kie.internal.task.api.assignment.AssignmentStrategy; import org.kie.internal.task.api.model.InternalPeopleAssignments; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Assignment strategy that assigns tasks to least occupied user from potential owners. * Least occupied is based on number of active tasks given user is assigned to - user is actual owner of the task. * * This strategy requires <code>GroupResolver</code> to be able to transform group potential owners into users list only. */ public class PotentialOwnerBusynessAssignmentStrategy implements AssignmentStrategy { private static final Logger logger = LoggerFactory.getLogger(PotentialOwnerBusynessAssignmentStrategy.class); public static final String IDENTIFIER = "PotentialOwnerBusyness"; private static final String DEFAULT_QUERY = "select new org.jbpm.services.task.assignment.impl.AssignmentImpl(t.taskData.actualOwner.id, count(t)) " + "from TaskImpl t " + "where t.taskData.actualOwner.id in (:owners) and t.taskData.status in ('Reserved', 'InProgress', 'Suspended') " + "group by t.taskData.actualOwner " + "order by count(t) asc, t.taskData.actualOwner.id asc"; public PotentialOwnerBusynessAssignmentStrategy() { } @Override public Assignment apply(Task task, TaskContext context, String excludedUser) { if (task.getPeopleAssignments().getPotentialOwners().isEmpty()) { logger.debug("No potential owners in the task {} can't auto assign", task); return null; } List<OrganizationalEntity> potentialOwners = new ArrayList<>(task.getPeopleAssignments().getPotentialOwners()); Set<String> resolvedUsers = new TreeSet<>(Collections.reverseOrder()); List<OrganizationalEntity> excludedOwners = ((InternalPeopleAssignments)task.getPeopleAssignments()).getExcludedOwners(); potentialOwners.stream().filter(po -> po instanceof User && !excludedOwners.contains(po)).forEach(po -> resolvedUsers.add(po.getId())); UserInfo userInfo = (UserInfo) ((org.jbpm.services.task.commands.TaskContext)context).get(EnvironmentName.TASK_USER_INFO); if (userInfo != null) { logger.debug("Groups going to be resolved by {}", userInfo); potentialOwners.stream().filter(po -> po instanceof Group && !excludedOwners.contains(po)).forEach(po -> { Iterator<OrganizationalEntity> usersOfGroup = userInfo.getMembersForGroup((Group)po); if (usersOfGroup != null) { while(usersOfGroup.hasNext()) { OrganizationalEntity entity = usersOfGroup.next(); if (!excludedOwners.contains(entity)) { resolvedUsers.add(entity.getId()); } } } }); } logger.debug("Resolved users eligible for task {} assignments are {}", task, resolvedUsers); if (excludedUser != null) { logger.debug("Removing excluded user {} from the list of eligible users", excludedUser); resolvedUsers.remove(excludedUser); } TaskPersistenceContext persistenceContext = ((org.jbpm.services.task.commands.TaskContext)context).getPersistenceContext(); Map<String, Object> params = new HashMap<>(); params.put("owners", resolvedUsers); logger.debug("DB query to be used for finding assignments :: '{}'", getQuery()); List<Assignment> assignments = persistenceContext.queryStringWithParametersInTransaction(getQuery(), params, ClassUtil.<List<Assignment>>castClass(List.class)); if (assignments.size() < resolvedUsers.size()) { logger.debug("Not all eligible users found in db, adding missing bits (eligible {}, found in db {})", resolvedUsers, assignments); // in case not all users have already assigned tasks added them to the list so can get the tasks resolvedUsers.forEach(user -> { Assignment assignment = new AssignmentImpl(user); if (!assignments.contains(assignment)) { // always add missing users to the top of the list so they get assigned first assignments.add(0, assignment); } }); } if (assignments.isEmpty()) { logger.debug("No assignments found for task {}", task); return null; } logger.debug("Following assignments {} were found for task {}", assignments, task); // select first from the top of the list as it has the least assigned tasks Assignment selected = assignments.get(0); logger.debug("Retruning first of found assignments {}", selected); return new Assignment(selected.getUser()); } protected String getQuery() { return DEFAULT_QUERY; } @Override public String getIdentifier() { return IDENTIFIER; } @Override public String toString() { return "AssignmentStrategy:: " + IDENTIFIER; } }