/*
* Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* 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.wso2.carbon.bpmn.people.substitution;
import org.activiti.engine.*;
import org.activiti.engine.task.IdentityLink;
import org.activiti.engine.task.IdentityLinkType;
import org.activiti.engine.task.Task;
import org.activiti.engine.task.TaskQuery;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.wso2.carbon.bpmn.core.BPMNConstants;
import org.wso2.carbon.bpmn.core.BPMNServerHolder;
import org.wso2.carbon.bpmn.core.mgt.dao.ActivitiDAO;
import org.wso2.carbon.bpmn.core.mgt.model.PaginatedSubstitutesDataModel;
import org.wso2.carbon.bpmn.core.mgt.model.SubstitutesDataModel;
import org.wso2.carbon.bpmn.core.utils.BPMNActivitiConfiguration;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;
import java.util.*;
import java.util.concurrent.*;
public class UserSubstitutionUtils {
private static final Log log = LogFactory.getLog(UserSubstitutionUtils.class);
public static final String LIST_SEPARATOR = ",";
public static final String TRUE = "true";
/**
* Persist the substitute info. Transitive substitute is not added here.
* @param assignee - User becomes unavailable
* @param substitute - substitute for the assignee
* @param startDate - start od the substitution
* @param endDate - end of the substitution
* @param taskListString - Comma separated String of task Ids
* @return added row count.
* @throws SubstitutionException
*/
public static SubstitutesDataModel addSubstituteInfo(String assignee, String substitute, Date startDate,
Date endDate, String taskListString, int tenantId) throws SubstitutionException {
ActivitiDAO activitiDAO = SubstitutionDataHolder.getInstance().getActivitiDAO();
//at any given time there could be only one substitute for a single user
if (activitiDAO.selectSubstituteInfo(assignee, tenantId) != null) {
log.error("Substitute for user: " + assignee + ", already exist. Try to update the substitute info");
throw new SubstitutionException(
"Substitute for user: " + assignee + ", already exist. Try to update the substitute info");
} else {
SubstitutesDataModel dataModel = new SubstitutesDataModel();
dataModel.setUser(assignee);
dataModel.setSubstitute(MultitenantUtils.getTenantAwareUsername(substitute));
dataModel.setSubstitutionStart(startDate);
if (endDate == null) {
endDate = getEndTimeMaxDate();
}
dataModel.setSubstitutionEnd(endDate);
dataModel.setEnabled(true); //by default enabled
dataModel.setCreated(new Date());
dataModel.setTenantId(tenantId);
dataModel.setTaskList(taskListString);
activitiDAO.insertSubstitute(dataModel);
return dataModel;
}
}
/**
* Handles addition of new substitute record and it's post conditions.
* @param assignee
* @param substitute
* @param startTime
* @param endTime
* @param enabled
* @param taskList
* @throws SubstitutionException
*/
public static void handleNewSubstituteAddition(String assignee, String substitute, Date startTime, Date endTime,
boolean enabled, List<String> taskList, int tenantId) throws SubstitutionException {
ActivitiDAO activitiDAO = SubstitutionDataHolder.getInstance().getActivitiDAO();
String taskListStr = getTaskListString(taskList);
SubstitutesDataModel dataModel = addSubstituteInfo(assignee, substitute, startTime, endTime, taskListStr, tenantId);
if (dataModel.isEnabled() && isBeforeActivationInterval(dataModel.getSubstitutionStart())) {
boolean transitivityResolved = updateTransitiveSubstitutes(dataModel, tenantId);
if (!transitivityResolved) {
//remove added transitive record
activitiDAO.removeSubstitute(assignee, tenantId);
throw new SubstitutionException( //SubstitutionException
"Could not find an available substitute. Use a different user to substitute");
}
if (SubstitutionDataHolder.getInstance().isTransitivityEnabled()) {
//transitive substitute maybe changed, need to retrieve again.
dataModel = activitiDAO.selectSubstituteInfo(dataModel.getUser(), dataModel.getTenantId());
}
if (!SubstitutionDataHolder.getInstance().isTransitivityEnabled() || BPMNConstants.TRANSITIVE_SUB_NOT_APPLICABLE
.equals(dataModel.getTransitiveSub())) {
bulkReassign(dataModel.getUser(), dataModel.getSubstitute(), taskList);
} else {
bulkReassign(dataModel.getUser(), dataModel.getTransitiveSub(), taskList);
}
}
}
/**
* Return a LIST_SEPARATOR separated String from given list
* @param taskList
* @return a LIST_SEPARATOR separated String from given item list
*/
private static String getTaskListString(List<String> taskList) {
if (taskList != null && !taskList.isEmpty()) {
StringBuffer list = new StringBuffer();
for (String id : taskList) {
list.append(id).append(LIST_SEPARATOR);
}
return list.toString();
} else {
return null;
}
}
/**
* Update all the transitive substitute fields if required
* @param dataModel - dataModel of user getting unavailable
*/
private static boolean updateTransitiveSubstitutes(SubstitutesDataModel dataModel, int tenantId) {
TransitivityResolver resolver = SubstitutionDataHolder.getInstance().getTransitivityResolver();
if (resolver.isResolvingRequired(dataModel.getUser(), tenantId)) {
return resolver.resolveTransitiveSubs(false, tenantId);
} else {//need to update transitive sub for this user
return resolver.resolveSubstituteForSingleUser(dataModel, tenantId);
}
}
private static boolean isBeforeActivationInterval(Date substitutionStart) {
long timeToNextScheduledEvent = BPMNServerHolder.getInstance().getSubstitutionScheduler().getNextScheduledTime();
Date bufferedTime = new Date(System.currentTimeMillis() + timeToNextScheduledEvent);
if (substitutionStart.compareTo(bufferedTime) < 0) {
return true;
} else {
return false;
}
}
/**
* Reassign the given tasks or all the tasks if the given task list is null, to the given substitute
* @param assignee - original user of the tasks
* @param substitute - user who getting assigned
* @param taskList - list of tasks to reassign. Leave this null to reassign all tha tasks of the assignee.
*/
public static void bulkReassign(String assignee, String substitute, List<String> taskList) {
if (taskList != null) { //reassign the given tasks
reassignFromTaskIdsList(taskList, substitute);
} else { //reassign all existing tasks for assignee
TaskQuery taskQuery = BPMNServerHolder.getInstance().getEngine().getTaskService().createTaskQuery();
taskQuery.taskAssignee(assignee);
reassignFromTasksList(taskQuery.list(), substitute);
transformUnclaimedTasks(assignee, substitute);
}
//should mark bulk reassign done
}
/**
* Look for all the tasks the assignee is a candidate and add substitute as a candidate user.
* @param assignee
* @param substitute
*/
private static void transformUnclaimedTasks(String assignee, String substitute) {
TaskQuery taskQuery = BPMNServerHolder.getInstance().getEngine().getTaskService().createTaskQuery();
taskQuery.taskCandidateUser(assignee);
List<Task> candidateTasks = taskQuery.list();
addAsCandidate(candidateTasks, substitute);
}
/**
* Check whether each given task is a candidate for substitution by assignee
* @param taskList
* @param assignee
*/
public static boolean validateTasksList(List<String> taskList, String assignee) {
if (taskList != null) {
TaskQuery taskQuery = BPMNServerHolder.getInstance().getEngine().getTaskService().createTaskQuery();
for (String taskId : taskList) {
taskQuery.taskId(taskId);
taskQuery.taskAssignee(assignee);
List<Task> tasks = taskQuery.list();//this should return a task if valid
if (tasks == null || tasks.isEmpty()) {
return false;
}
}
}
return true;
}
private static void reassignFromTaskIdsList(final List<String> taskList, final String substitute) {
Thread reassignThread = new Thread() {
public void run() {
for (String taskId : taskList) {
BPMNServerHolder.getInstance().getEngine().getTaskService().setAssignee(taskId, substitute);
}
}
};
executeInThreadPool(reassignThread);
}
private static void executeInThreadPool(Runnable runnable) {
ExecutorService pool = Executors.newCachedThreadPool();
pool.execute(runnable);
}
private static void reassignFromTasksList(final List<Task> taskList, final String substitute) {
Thread reassignThread = new Thread() {
public void run() {
for (Task task : taskList) {
BPMNServerHolder.getInstance().getEngine().getTaskService().setAssignee(task.getId(), substitute);
}
}
};
executeInThreadPool(reassignThread);
}
private static void addAsCandidate(final List<Task> taskList, final String substitute) {
if (taskList == null || taskList.isEmpty()) {
return;
}
Thread reassignThread = new Thread() {
public void run() {
for (Task task : taskList) {
BPMNServerHolder.getInstance().getEngine().getTaskService().addCandidateUser(task.getId(), substitute);
}
}
};
executeInThreadPool(reassignThread);
}
public static void handleUpdateSubstitute(String assignee, String substitute, Date startTime, Date endTime,
boolean enabled, List<String> taskList, int tenantId) {
ActivitiDAO activitiDAO = SubstitutionDataHolder.getInstance().getActivitiDAO();
SubstitutesDataModel existingSubInfo = activitiDAO.selectSubstituteInfo(assignee, tenantId);
if (existingSubInfo != null) {
//need to put existing values for null columns, if not existing data may replace by Null
if (startTime == null) {
startTime = existingSubInfo.getSubstitutionStart();
}
if (endTime == null) {
endTime = existingSubInfo.getSubstitutionEnd();
}
String taskListString = getTaskListString(taskList);
if (taskList == null) {
taskListString = existingSubInfo.getTaskList();
}
SubstitutesDataModel dataModel = updateSubstituteInfo(assignee, substitute, startTime, endTime,
taskListString, tenantId);
if (dataModel.isEnabled() && isBeforeActivationInterval(dataModel.getSubstitutionStart())) {
boolean transitivityResolved = updateTransitiveSubstitutes(dataModel, tenantId);
if (!transitivityResolved) {
//remove added transitive record
activitiDAO.updateSubstituteInfo(existingSubInfo);
throw new SubstitutionException(
"Could not find an available substitute. Use a different user to substitute");
}
if (SubstitutionDataHolder.getInstance().isTransitivityEnabled()) {
//transitive substitute maybe changed, need to retrieve again.
dataModel = activitiDAO.selectSubstituteInfo(dataModel.getUser(), dataModel.getTenantId());
}
if (!SubstitutionDataHolder.getInstance().isTransitivityEnabled() || BPMNConstants.TRANSITIVE_SUB_NOT_APPLICABLE
.equals(dataModel.getTransitiveSub())) {
bulkReassign(dataModel.getUser(), dataModel.getSubstitute(), taskList);
} else {
bulkReassign(dataModel.getUser(), dataModel.getTransitiveSub(), taskList);
}
}
} else {
throw new SubstitutionException(
"Substitute for user: " + assignee + ", does not exist. Try to add new substitute info record");
}
}
private static SubstitutesDataModel updateSubstituteInfo(String assignee, String substitute, Date startTime,
Date endTime, String taskListString, int tenantId) {
SubstitutesDataModel dataModel = new SubstitutesDataModel();
dataModel.setUser(assignee);
dataModel.setSubstitute(substitute);
dataModel.setSubstitutionStart(startTime);
dataModel.setSubstitutionEnd(endTime);
dataModel.setEnabled(true); //by default enabled
dataModel.setTenantId(tenantId);
dataModel.setUpdated(new Date());
dataModel.setTaskList(taskListString);
SubstitutionDataHolder.getInstance().getActivitiDAO().updateSubstituteInfo(dataModel);
return dataModel;
}
public static void handleChangeSubstitute(String assignee, String substitute, int tenantId) {
ActivitiDAO activitiDAO = SubstitutionDataHolder.getInstance().getActivitiDAO();
SubstitutesDataModel existingSubInfo = activitiDAO.selectSubstituteInfo(assignee, tenantId);
if (existingSubInfo != null) {
activitiDAO.updateSubstitute(assignee, substitute, tenantId, new Date());
if (existingSubInfo.isEnabled() && isBeforeActivationInterval(existingSubInfo.getSubstitutionStart())) {
String existingSub = existingSubInfo.getSubstitute();
existingSubInfo.setSubstitute(substitute);
boolean transitivityResolved = updateTransitiveSubstitutes(existingSubInfo, tenantId);
if (!transitivityResolved) {
//remove added record
activitiDAO.updateSubstitute(assignee, existingSub, tenantId, existingSubInfo.getUpdated());
throw new SubstitutionException(
"Given Substitute is not available. Provide a different user to substitute.");
}
}
} else {
throw new SubstitutionException("No substitution record found for the user: " + assignee);
}
}
/**
* Get the substitute info of the given user.
* @param assignee
* @return SubstitutesDataModel
*/
public static SubstitutesDataModel getSubstituteOfUser(String assignee, int tenantId) {
SubstitutesDataModel dataModel = SubstitutionDataHolder.getInstance().getActivitiDAO().selectSubstituteInfo(assignee, tenantId);
Date maxDate = getEndTimeMaxDate();
//set null if max end date
if (dataModel != null && maxDate.compareTo(dataModel.getSubstitutionEnd()) == 0) {
dataModel.setSubstitutionEnd(null);
}
return dataModel;
}
private static Date getEndTimeMaxDate() {
DateTime dateTime = new DateTime(SubstitutionDataHolder.getInstance().getSubstitutionMaxEpoch(), DateTimeZone.UTC);
Date maxDate = new Date(dateTime.toDateTime(DateTimeZone.getDefault()).getMillis());
return maxDate;
}
/**
* Query substitution records by given properties.
* Allowed properties: user, substitute, enabled.
* Pagination parameters : start, size, sort, order
* @param propertiesMap
* @return Paginated list of PaginatedSubstitutesDataModel
*/
public static List<SubstitutesDataModel> querySubstitutions(Map<String, String> propertiesMap, int tenantId) {
ActivitiDAO activitiDAO = SubstitutionDataHolder.getInstance().getActivitiDAO();
PaginatedSubstitutesDataModel model = getPaginatedModelFromRequest(propertiesMap, tenantId);
String enabled = propertiesMap.get(SubstitutionQueryProperties.ENABLED);
boolean enabledProvided = false;
if (enabled != null) {
enabledProvided = true;
}
if (!enabledProvided) {
return prepareEndTime(activitiDAO.querySubstituteInfoWithoutEnabled(model));
} else {
return prepareEndTime(activitiDAO.querySubstituteInfo(model));
}
}
/**
* If the endDate is set to BPMNConstants.SUBSTITUTION_MAX_END_DATE_EPOCH, it is changed to null
* @param modelList
* @return
*/
private static List<SubstitutesDataModel> prepareEndTime(List<SubstitutesDataModel> modelList) {
for (SubstitutesDataModel model : modelList) {
if (model.getSubstitutionEnd()!= null && getEndTimeMaxDate().compareTo(model.getSubstitutionEnd()) == 0) {
model.setSubstitutionEnd(null);
}
}
return modelList;
}
/**
* Total count of query substitution result by given properties.
* Allowed properties: user, substitute, enabled.
* Pagination parameters : start, size, sort, order
* @param propertiesMap
* @return Total count of query result
*/
public static int getQueryResultCount(Map<String, String> propertiesMap, int tenantId) {
ActivitiDAO activitiDAO = SubstitutionDataHolder.getInstance().getActivitiDAO();
PaginatedSubstitutesDataModel model = getPaginatedModelFromRequest(propertiesMap, tenantId);
String enabled = propertiesMap.get(SubstitutionQueryProperties.ENABLED);
boolean enabledProvided = false;
if (enabled != null) {
enabledProvided = true;
}
if (!enabledProvided) {
return activitiDAO.selectQueryResultCountWithoutEnabled(model);
} else {
return activitiDAO.selectQueryResultCount(model);
}
}
/**
* Prepare the paginated data model for a substitution query
* @param propertiesMap
* @param tenantId
* @return PaginatedSubstitutesDataModel
*/
private static PaginatedSubstitutesDataModel getPaginatedModelFromRequest(Map<String, String> propertiesMap, int tenantId) {
PaginatedSubstitutesDataModel model = new PaginatedSubstitutesDataModel();
if (propertiesMap.get(SubstitutionQueryProperties.SUBSTITUTE) != null) {
model.setSubstitute(propertiesMap.get(SubstitutionQueryProperties.SUBSTITUTE));
}
if (propertiesMap.get(SubstitutionQueryProperties.USER) != null) {
model.setUser(propertiesMap.get(SubstitutionQueryProperties.USER));
}
String enabled = propertiesMap.get(SubstitutionQueryProperties.ENABLED);
if (enabled != null) {
if (enabled.equalsIgnoreCase("true")) {
model.setEnabled(true);
} else if (enabled.equalsIgnoreCase("false")) {
model.setEnabled(false);
} else {
throw new ActivitiIllegalArgumentException("Invalid parameter " + enabled + " for enabled property.");
}
}
model.setTenantId(tenantId);
int start = Integer.parseInt(propertiesMap.get(SubstitutionQueryProperties.START));
int size = Integer.parseInt(propertiesMap.get(SubstitutionQueryProperties.SIZE));
model.setStart(start);
model.setSize(size);
model.setOrder(propertiesMap.get(SubstitutionQueryProperties.ORDER));
model.setSort(propertiesMap.get(SubstitutionQueryProperties.SORT));
return model;
}
/**
* Return the maximum activation interval for a substitution.
* @return activation interval in milliseconds
*/
public static long getActivationInterval() {
long activationInterval = BPMNConstants.DEFAULT_SUBSTITUTION_INTERVAL_IN_MINUTES * 60 * 1000;
BPMNActivitiConfiguration bpmnActivitiConfiguration = BPMNActivitiConfiguration.getInstance();
if (bpmnActivitiConfiguration != null) {
String activationIntervalString = bpmnActivitiConfiguration
.getBPMNPropertyValue(BPMNConstants.SUBSTITUTION_CONFIG,
BPMNConstants.SUBSTITUTION_SCHEDULER_INTERVAL);
if (activationIntervalString != null) {
activationInterval = Long.parseLong(activationIntervalString) * 60 * 1000;
if (log.isDebugEnabled()) {
log.debug("Using the substitution activation interval : " + activationIntervalString + " minutes");
}
}
}
return activationInterval;
}
public synchronized static boolean handleScheduledEventByTenant(int tenantId) {
boolean result = true;
TransitivityResolver resolver = SubstitutionDataHolder.getInstance().getTransitivityResolver();
ActivitiDAO activitiDAO = SubstitutionDataHolder.getInstance().getActivitiDAO();
if (SubstitutionDataHolder.getInstance().isTransitivityEnabled()) {
result = resolver.resolveTransitiveSubs(true, tenantId); //update transitives, only the map is updated here
} else {
resolver.subsMap = activitiDAO.selectActiveSubstitutesByTenant(tenantId, new Date(System.currentTimeMillis()));
}
//bulk reassign
//flush into db
for (Map.Entry<String, SubstitutesDataModel> entry : resolver.subsMap.entrySet()) { //go through the updated map
SubstitutesDataModel model = entry.getValue();
try {
//set carbon context
PrivilegedCarbonContext.startTenantFlow();
PrivilegedCarbonContext context = PrivilegedCarbonContext.getThreadLocalCarbonContext();
context.setUsername(model.getUser());
context.setTenantId(tenantId, true);
if (SubstitutionDataHolder.getInstance().isTransitivityEnabled()) {
activitiDAO.updateSubstituteInfo(model);
}
if (!BPMNConstants.BULK_REASSIGN_PROCESSED.equals(model.getTaskList())) { //active substitution, not yet bulk reassigned
String sub = getActualSubstitute(model);
if (model.getTaskList() == null) {//reassign all
if (sub != null) {
bulkReassign(model.getUser(), sub, null);
} else {//transitivity undefined, assign to task owner or un-claim
assignToTaskOwner(model.getUser(), null);
}
} else {
List<String> taskList = getTaskListFromString(model.getTaskList());
if (sub != null) {
bulkReassign(model.getUser(), sub, taskList);
} else {//transitivity undefined, assign to task owner or un-claim
assignToTaskOwner(model.getUser(), taskList);
}
}
model.setTaskList(BPMNConstants.BULK_REASSIGN_PROCESSED);
activitiDAO.updateSubstituteInfo(model);
}
} finally {
PrivilegedCarbonContext.endTenantFlow();
PrivilegedCarbonContext.destroyCurrentContext();
}
}
//disable expired records
disableExpiredRecords(tenantId);
return result;
}
/**
* Disable the records that are still enabled but expired
* @param tenantId
*/
public static void disableExpiredRecords(int tenantId) {
ActivitiDAO activitiDAO = SubstitutionDataHolder.getInstance().getActivitiDAO();
Map<String, SubstitutesDataModel> map = activitiDAO.getEnabledExpiredRecords(tenantId, new Date(System.currentTimeMillis()));
for (Map.Entry<String, SubstitutesDataModel> entry : map.entrySet()) {
activitiDAO.enableSubstitution(false, entry.getKey(), tenantId);
}
}
/**
* Handle the transitivity resolving, disabling expired records and task reassignments for all substitutions.
* @return true if successfully completed
*/
public static boolean handleScheduledEvent() {
//should do this for each tenant that has substitutions
List<Integer> tenantList = getTenantsList();
if (tenantList != null && !tenantList.isEmpty()) {
for (int tenantId : tenantList) {
if (!handleScheduledEventByTenant(tenantId)) {
return false;
}
}
}
return true;
}
/**
* Get the list of tenants that has substitutions
* @return List<Integer> tenantID list
*/
public static List<Integer> getTenantsList() {
return SubstitutionDataHolder.getInstance().getActivitiDAO().getTenantsList();
}
private static void assignToTaskOwner(String assignee, List<String> taskList) {
TaskService taskService = BPMNServerHolder.getInstance().getEngine().getTaskService();
if (taskList != null) {
for (String taskId : taskList) {
String taskOwner = null;
List<IdentityLink> identityLinks = taskService.getIdentityLinksForTask(taskId);
for (IdentityLink link : identityLinks) {
if(IdentityLinkType.OWNER.equals(link.getType())) {
taskOwner = link.getUserId();
}
}
if (taskOwner != null) {//assign to task owner
taskService.setAssignee(taskId, taskOwner);
} else {
taskService.addCandidateUser(taskId, assignee);
taskService.unclaim(taskId);
}
}
} else {//reassign all tasks
TaskQuery taskQuery = taskService.createTaskQuery();
taskQuery.taskAssignee(assignee);
List<Task> list = taskQuery.list();
for (Task task : list) {
String taskOwner = task.getOwner();
if (taskOwner != null) {//assign to task owner
taskService.setAssignee(task.getId(), taskOwner);
} else {
taskService.addCandidateUser(task.getId(), assignee);
taskService.unclaim(task.getId());
}
}
}
}
private static List<String> getTaskListFromString(String taskList) {
return Arrays.asList(taskList.split("\\s*,\\s*"));
}
private static String getActualSubstitute(SubstitutesDataModel model) {
if (!SubstitutionDataHolder.getInstance().isTransitivityEnabled() || BPMNConstants.TRANSITIVE_SUB_NOT_APPLICABLE
.equals(model.getTransitiveSub())) {
return model.getSubstitute();
} else if (BPMNConstants.TRANSITIVE_SUB_UNDEFINED.equals(model.getTransitiveSub())){
return null;
} else {
return model.getTransitiveSub();
}
}
/**
* Check if an active substitution available for given substitute info
* @param substitutesDataModel
* @return true if substitution active
*/
private static boolean isSubstitutionActive(SubstitutesDataModel substitutesDataModel) {
long startDate = substitutesDataModel.getSubstitutionStart().getTime();
long endDate = substitutesDataModel.getSubstitutionEnd().getTime();
long currentTime = System.currentTimeMillis();
if ((startDate < currentTime) && (endDate > currentTime) && substitutesDataModel.isEnabled() ) {
return true;
}
return false;
}
/**
* Disable the the substitution record of the given assignee
* @param disable - true to disable
* @param assignee - user of the substitution
* @param tenantId - assignee's tenant id
*/
public static void disableSubstitution(boolean disable, String assignee, int tenantId) {
ActivitiDAO activitiDAO = SubstitutionDataHolder.getInstance().getActivitiDAO();
if (activitiDAO.selectSubstituteInfo(assignee, tenantId) != null) {
activitiDAO.enableSubstitution(!disable, assignee, tenantId);
} else {
throw new ActivitiIllegalArgumentException("No substitution record exist for the given user : " + assignee);
}
}
}