/* * 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.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; import java.util.stream.Collectors; 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.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; public class RoundRobinAssignmentStrategy implements AssignmentStrategy { private static final Logger logger = LoggerFactory.getLogger(RoundRobinAssignmentStrategy.class); private static final String IDENTIFIER = "RoundRobin"; private Map<String, CircularQueue<OrganizationalEntity>> circularQueueMap = new ConcurrentHashMap<>(); private class CircularQueue<T> extends LinkedBlockingQueue<T> { @Override public synchronized T take() { T headValue = null; try { headValue = super.take(); super.offer(headValue); } catch (InterruptedException e) { logger.error("Thread interrupted during the 'take' from a circular queue in the " + "RoundRobinAssignmentStrategy", e); } return headValue; } } @Override public String getIdentifier() { return IDENTIFIER; } @Override public Assignment apply(Task task, TaskContext taskContext, String excludedUser) { UserInfo userInfo = (UserInfo) ((org.jbpm.services.task.commands.TaskContext) taskContext).get(EnvironmentName.TASK_USER_INFO); List<OrganizationalEntity> excluded = getExcludedEntities(task, userInfo); // Get the the users from the task's the potential owners List<OrganizationalEntity> potentialOwners = task.getPeopleAssignments().getPotentialOwners().stream() .filter(oe -> oe instanceof User && !excluded.contains(oe)) .collect(Collectors.toList()); // Get the users belonging to groups that are potential owners task.getPeopleAssignments().getPotentialOwners().stream().filter(oe -> oe instanceof Group) .forEach(oe -> { Iterator<OrganizationalEntity> groupUsers = userInfo.getMembersForGroup((Group) oe); if (groupUsers != null) { groupUsers.forEachRemaining(user -> { if (user != null && !excluded.contains(user) && !potentialOwners.contains(user)) { potentialOwners.add(user); } }); } }); if (excludedUser != null) { logger.debug("Removing excluded user {} from the list of eligible users", excludedUser); potentialOwners.removeIf(entity -> entity.getId().equals(excludedUser)); } String queueName = getQueueName(task); CircularQueue<OrganizationalEntity> mappedQueue = synchronizedQueue(queueName, potentialOwners); OrganizationalEntity owner = mappedQueue.take(); return new Assignment(owner.getId()); } /** * Synchronizes the {@code OrganizationalEntity} objects contained in the {@code CircularQueue} and the list of * potential owners * @param queueName The name of the queue to be synchronized. * @param potentialOwners This list of potential owners of the task * @return The CircularQueue that contains all potential owners */ private synchronized CircularQueue<OrganizationalEntity> synchronizedQueue(String queueName, List<OrganizationalEntity> potentialOwners) { CircularQueue<OrganizationalEntity> existingQueue = (queueName == null || queueName.trim().length() == 0) ? null : circularQueueMap.get(queueName); // If the queue does not exist then a new CircularQueue should be created final CircularQueue<OrganizationalEntity> workingQueue = existingQueue != null ? existingQueue : new CircularQueue(); potentialOwners.forEach(po -> { if (!queueContainsUser(workingQueue, po)) { workingQueue.add(po); } }); workingQueue.removeIf(oe -> !potentialOwners.contains(oe)); circularQueueMap.put(queueName, workingQueue); return workingQueue; } protected boolean queueContainsUser(CircularQueue<OrganizationalEntity> queue, OrganizationalEntity oe) { return queue.contains(oe); } /** * Generates a queue name that is based on data retrieved from the task. The form of the generated queue name is: * Process ID + "_" + Deployment ID + " " + Task Name * @param task Source of the data used to generate the queue name * @return The generated queue name */ protected String getQueueName(Task task) { return task.getTaskData().getProcessId() + "_" + task.getTaskData().getDeploymentId() + "_" + task.getName(); } private static List<OrganizationalEntity> getExcludedEntities(Task task, UserInfo userInfo) { List<OrganizationalEntity> excluded = ((InternalPeopleAssignments) task.getPeopleAssignments()).getExcludedOwners(); List<OrganizationalEntity> excludedUsers = new ArrayList<>(); for (OrganizationalEntity entity : excluded) { if (entity instanceof Group) { userInfo.getMembersForGroup((Group) entity).forEachRemaining(excludedUsers::add); } } excluded.addAll(excludedUsers); return excluded; } }