/*
* Copyright (c) 2010-2013 Evolveum
*
* Licensed 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 com.evolveum.midpoint.wf.impl.tasks;
import com.evolveum.midpoint.model.api.context.ModelContext;
import com.evolveum.midpoint.model.impl.controller.ModelOperationTaskHandler;
import com.evolveum.midpoint.model.impl.lens.LensContext;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
import com.evolveum.midpoint.task.api.*;
import com.evolveum.midpoint.util.DebugDumpable;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.wf.impl.processes.ProcessMidPointInterface;
import com.evolveum.midpoint.wf.impl.processes.common.ActivitiUtil;
import com.evolveum.midpoint.wf.impl.processes.common.LightweightObjectRef;
import com.evolveum.midpoint.wf.impl.processes.common.LightweightObjectRefImpl;
import com.evolveum.midpoint.wf.impl.processors.ChangeProcessor;
import com.evolveum.midpoint.wf.impl.util.MiscDataUtil;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;
import org.apache.commons.lang.Validate;
import java.util.*;
import static com.evolveum.midpoint.prism.xml.XmlTypeConverter.createXMLGregorianCalendar;
import static com.evolveum.midpoint.schema.util.ObjectTypeUtil.createObjectRef;
import static com.evolveum.midpoint.wf.impl.processes.common.CommonProcessVariableNames.*;
/**
* A generic instruction to start a background task; with or without a workflow process instance.
* May be subclassed in order to add further functionality.
*
* @author mederly
*/
public class WfTaskCreationInstruction<PRC extends ProcessorSpecificContent, PCS extends ProcessSpecificContent> implements DebugDumpable {
private static final Trace LOGGER = TraceManager.getTrace(WfTaskCreationInstruction.class);
private static final Integer DEFAULT_PROCESS_CHECK_INTERVAL = 30;
private final ChangeProcessor changeProcessor;
protected final WfContextType wfContext = new WfContextType(); // workflow context to be put into the task
private ModelContext taskModelContext; // model context to be put into the task
private final Date processCreationTimestamp = new Date();
protected final PRC processorContent;
private final PCS processContent;
private PrismObject taskObject; // object to be attached to the task; this object must have its definition available
private PrismObject<UserType> taskOwner; // if null, owner from parent task will be taken (if there's no parent task, exception will be thrown)
private PolyStringType taskName; // name of task to be created/updated (applies only if the task has no name already) - e.g. "Approve adding role R to U"
private boolean executeModelOperationHandler; // should the task contain model operation to be executed?
private boolean noProcess; // should the task provide no wf process (only direct execution of model operation)?
private boolean simple; // is workflow process simple? (i.e. such that requires periodic watching of its state)
private boolean sendStartConfirmation = true; // should we send explicit "process started" event when the process was started by midPoint?
// for listener-enabled processes this can be misleading, because "process started" event could come
// after "process finished" one (for immediately-finishing processes)
//
// unfortunately, it seems we have to live with this (unless we define a "process started" listener)
private TaskExecutionStatus taskInitialState = TaskExecutionStatus.RUNNABLE;
// what should be executed at a given occasion (in the order of being in this list)
private final List<UriStackEntry> handlersAfterModelOperation = new ArrayList<>();
private final List<UriStackEntry> handlersBeforeModelOperation = new ArrayList<>();
private final List<UriStackEntry> handlersAfterWfProcess = new ArrayList<>();
//region Constructors
protected WfTaskCreationInstruction(ChangeProcessor changeProcessor, PRC processorContent, PCS processContent) {
Validate.notNull(changeProcessor);
this.changeProcessor = changeProcessor;
this.processorContent = processorContent;
this.processContent = processContent;
}
@SuppressWarnings("unchecked")
public static WfTaskCreationInstruction<?,?> createModelOnly(ChangeProcessor changeProcessor, ModelContext modelContext) throws SchemaException {
WfTaskCreationInstruction<?,?> instruction = new WfTaskCreationInstruction(changeProcessor, null, null);
instruction.setNoProcess(true);
instruction.setTaskModelContext(modelContext);
instruction.setExecuteModelOperationHandler(true);
return instruction;
}
@SuppressWarnings("unchecked")
public static WfTaskCreationInstruction<?,?> createWfOnly(ChangeProcessor changeProcessor,
ProcessorSpecificContent processorSpecificContent, ProcessSpecificContent processSpecificContent) {
return new WfTaskCreationInstruction(changeProcessor, processorSpecificContent, processSpecificContent);
}
@SuppressWarnings("unchecked")
public static WfTaskCreationInstruction<?,?> createEmpty(ChangeProcessor changeProcessor) throws SchemaException {
WfTaskCreationInstruction<?,?> instruction = new WfTaskCreationInstruction(changeProcessor, null, null);
instruction.setNoProcess(true);
return instruction;
}
//endregion
// region Getters and setters
public ChangeProcessor getChangeProcessor() {
return changeProcessor;
}
protected PrismContext getPrismContext() { return changeProcessor.getPrismContext(); }
public void setSimple(boolean simple) {
this.simple = simple;
}
public boolean isSendStartConfirmation() {
return sendStartConfirmation;
}
public void setSendStartConfirmation(boolean sendStartConfirmation) {
this.sendStartConfirmation = sendStartConfirmation;
}
public String getProcessName() {
return wfContext.getProcessName();
}
public void setProcessName(String name) {
wfContext.setProcessName(name);
}
public String getProcessInstanceName() {
return wfContext.getProcessInstanceName();
}
public void setProcessInstanceName(String name) {
wfContext.setProcessInstanceName(name);
}
public void setTaskName(String taskName) {
this.taskName = new PolyStringType(taskName);
}
public boolean isNoProcess() {
return noProcess;
}
public boolean startsWorkflowProcess() {
return !noProcess;
}
public void setNoProcess(boolean noProcess) {
this.noProcess = noProcess;
}
public void setCreateTaskAsSuspended() {
this.taskInitialState = TaskExecutionStatus.SUSPENDED;
}
public void setCreateTaskAsWaiting() {
this.taskInitialState = TaskExecutionStatus.WAITING;
}
public List<UriStackEntry> getHandlersAfterModelOperation() {
return handlersAfterModelOperation;
}
public List<UriStackEntry> getHandlersBeforeModelOperation() {
return handlersBeforeModelOperation;
}
public List<UriStackEntry> getHandlersAfterWfProcess() {
return handlersAfterWfProcess;
}
public void setHandlersBeforeModelOperation(String... handlerUri) {
setHandlers(handlersBeforeModelOperation, createUriStackEntries(handlerUri));
}
public void setHandlersAfterModelOperation(String... handlerUri) {
setHandlers(handlersAfterModelOperation, createUriStackEntries(handlerUri));
}
public void addHandlersAfterWfProcessAtEnd(String... handlerUriArray) {
addHandlersAtEnd(handlersAfterWfProcess, createUriStackEntries(handlerUriArray));
}
private List<UriStackEntry> createUriStackEntries(String[] handlerUriArray) {
List<UriStackEntry> retval = new ArrayList<>();
for (String handlerUri : handlerUriArray) {
retval.add(createUriStackEntry(handlerUri));
}
return retval;
}
private UriStackEntry createUriStackEntry(String handlerUri, TaskRecurrence recurrence, ScheduleType scheduleType, TaskBinding taskBinding) {
UriStackEntry uriStackEntry = new UriStackEntry();
uriStackEntry.setHandlerUri(handlerUri);
uriStackEntry.setRecurrence(recurrence != null ? recurrence.toTaskType() : null);
uriStackEntry.setSchedule(scheduleType);
uriStackEntry.setBinding(taskBinding != null ? taskBinding.toTaskType() : null);
return uriStackEntry;
}
private UriStackEntry createUriStackEntry(String handlerUri) {
return createUriStackEntry(handlerUri, TaskRecurrence.SINGLE, new ScheduleType(), null);
}
private void setHandlers(List<UriStackEntry> list, List<UriStackEntry> uriStackEntry) {
list.clear();
list.addAll(uriStackEntry);
}
private void addHandlersAtEnd(List<UriStackEntry> list, List<UriStackEntry> uriStackEntry) {
list.addAll(uriStackEntry);
}
public void setExecuteModelOperationHandler(boolean executeModelOperationHandler) {
this.executeModelOperationHandler = executeModelOperationHandler;
}
public void setTaskObject(PrismObject taskObject) {
this.taskObject = taskObject;
}
public void setTaskOwner(PrismObject<UserType> taskOwner) {
this.taskOwner = taskOwner;
}
public void setTaskModelContext(ModelContext taskModelContext) {
this.taskModelContext = taskModelContext;
}
public void setObjectRef(ObjectReferenceType ref, OperationResult result) {
ref = getChangeProcessor().getMiscDataUtil().resolveObjectReferenceName(ref, result);
wfContext.setObjectRef(ref);
}
public void setObjectRef(ModelContext<?> modelContext, OperationResult result) {
ObjectType focus = MiscDataUtil.getFocusObjectNewOrOld(modelContext);
setObjectRef(ObjectTypeUtil.createObjectRef(focus), result);
}
public void setTargetRef(ObjectReferenceType ref, OperationResult result) {
ref = getChangeProcessor().getMiscDataUtil().resolveObjectReferenceName(ref, result);
wfContext.setTargetRef(ref);
}
public void setRequesterRef(PrismObject<UserType> requester) {
wfContext.setRequesterRef(createObjectRef(requester));
}
public void setProcessInterfaceBean(ProcessMidPointInterface processInterfaceBean) {
wfContext.setProcessInterface(processInterfaceBean.getBeanName());
}
public PRC getProcessorContent() {
return processorContent;
}
public PCS getProcessContent() {
return processContent;
}
//endregion
//region Diagnostics
public String toString() {
return "WfTaskCreationInstruction: processDefinitionKey = " + getProcessName() + ", simple: " + simple;
}
@Override
public String debugDump() {
return debugDump(0);
}
@Override
public String debugDump(int indent) {
StringBuilder sb = new StringBuilder();
DebugUtil.indentDebugDump(sb, indent);
sb.append("WfTaskCreationInstruction: process: ").append(getProcessName()).append("/").append(getProcessInstanceName());
sb.append(" ").append(simple ? "simple" : "smart").append(", ").append(noProcess ? "no-process" : "with-process").append(", model-context: ");
sb.append(taskModelContext != null ? "YES" : "no").append(", task = ").append(taskName).append("\n");
DebugUtil.indentDebugDump(sb, indent);
sb.append("Workflow context:\n");
sb.append(wfContext.asPrismContainerValue().debugDump(indent+2)).append("\n");
DebugUtil.debugDumpWithLabelLn(sb, "Change processor", changeProcessor.getClass().getName(), indent+1);
DebugUtil.debugDumpWithLabelLn(sb, "Process creation timestamp", String.valueOf(processCreationTimestamp), indent+1);
DebugUtil.debugDumpWithLabelLn(sb, "Task object", String.valueOf(taskObject), indent+1);
DebugUtil.debugDumpWithLabelLn(sb, "Task owner", String.valueOf(taskOwner), indent+1);
DebugUtil.debugDumpWithLabelLn(sb, "Task initial state", String.valueOf(taskInitialState), indent+1);
DebugUtil.debugDumpWithLabelLn(sb, "Send start confirmation", String.valueOf(sendStartConfirmation), indent+1);
DebugUtil.debugDumpWithLabelLn(sb, "Handlers after model operation", String.valueOf(handlersAfterModelOperation), indent+1);
DebugUtil.debugDumpWithLabelLn(sb, "Handlers before model operation", String.valueOf(handlersBeforeModelOperation), indent+1);
DebugUtil.debugDumpWithLabelLn(sb, "Handlers after wf process", String.valueOf(handlersAfterWfProcess), indent+1);
DebugUtil.debugDumpWithLabelLn(sb, "Processor instruction", String.valueOf(processorContent), indent+1);
DebugUtil.debugDumpWithLabelLn(sb, "Process instruction", String.valueOf(processContent), indent+1);
return sb.toString();
}
//endregion
//region "Output" methods
public Task createTask(WfTaskController taskController, Task parentTask, WfConfigurationType wfConfigurationType) throws SchemaException {
LOGGER.trace("createTask starting; parent task = {}", parentTask);
final WfTaskUtil wfTaskUtil = taskController.getWfTaskUtil();
final Task task;
if (parentTask != null) {
task = parentTask.createSubtask();
} else {
task = taskController.getTaskManager().createTaskInstance();
if (taskOwner == null) {
throw new IllegalStateException("No task owner for " + task);
}
task.setOwner(taskOwner);
}
task.setInitialExecutionStatus(taskInitialState);
task.setCategory(TaskCategory.WORKFLOW);
if (taskObject != null) {
task.setObjectRef(taskObject.getOid(), taskObject.getDefinition().getTypeName());
} else if (parentTask != null && parentTask.getObjectRef() != null) {
task.setObjectRef(parentTask.getObjectRef());
}
if (task.getName() == null || task.getName().toPolyString().isEmpty()) {
task.setName(taskName);
}
// push the handlers, beginning with these that should execute last
wfTaskUtil.pushHandlers(task, getHandlersAfterModelOperation());
if (executeModelOperationHandler) {
task.pushHandlerUri(ModelOperationTaskHandler.MODEL_OPERATION_TASK_URI, null, null);
}
wfTaskUtil.pushHandlers(task, getHandlersBeforeModelOperation());
wfTaskUtil.pushHandlers(task, getHandlersAfterWfProcess());
if (!noProcess) {
if (simple) {
ScheduleType schedule = new ScheduleType();
Integer processCheckInterval = wfConfigurationType != null ? wfConfigurationType.getProcessCheckInterval() : null;
schedule.setInterval(processCheckInterval != null ? processCheckInterval : DEFAULT_PROCESS_CHECK_INTERVAL);
schedule.setEarliestStartTime(MiscUtil.asXMLGregorianCalendar(new Date(System.currentTimeMillis() + WfTaskController.TASK_START_DELAY)));
task.pushHandlerUri(WfProcessInstanceShadowTaskHandler.HANDLER_URI, schedule, TaskBinding.LOOSE);
} else {
task.pushHandlerUri(WfProcessInstanceShadowTaskHandler.HANDLER_URI, new ScheduleType(), null); // note that this handler will not be actively used (at least for now)
task.makeWaiting();
}
}
// model and workflow context
if (taskModelContext != null) {
task.setModelOperationContext(((LensContext) taskModelContext).toLensContextType());
}
wfContext.setChangeProcessor(changeProcessor.getClass().getName());
wfContext.setStartTimestamp(createXMLGregorianCalendar(processCreationTimestamp));
if (processorContent != null) {
wfContext.setProcessorSpecificState(processorContent.createProcessorSpecificState());
}
if (processContent != null) {
wfContext.setProcessSpecificState(processContent.createProcessSpecificState());
}
task.setWorkflowContext(wfContext);
return task;
}
public Map<String, Object> getAllProcessVariables() throws SchemaException {
Map<String, Object> map = new HashMap<>();
map.put(VARIABLE_PROCESS_INSTANCE_NAME, wfContext.getProcessInstanceName());
map.put(VARIABLE_START_TIME, processCreationTimestamp);
map.put(VARIABLE_OBJECT_REF, toLightweightObjectRef(wfContext.getObjectRef()));
map.put(VARIABLE_TARGET_REF, toLightweightObjectRef(wfContext.getTargetRef()));
map.put(VARIABLE_REQUESTER_REF, toLightweightObjectRef(wfContext.getRequesterRef()));
map.put(VARIABLE_CHANGE_PROCESSOR, changeProcessor.getClass().getName());
map.put(VARIABLE_PROCESS_INTERFACE_BEAN_NAME, wfContext.getProcessInterface());
map.put(VARIABLE_UTIL, new ActivitiUtil());
if (processorContent != null) {
processorContent.createProcessVariables(map, getPrismContext());
}
if (processContent != null) {
processContent.createProcessVariables(map, getPrismContext());
}
return map;
}
private LightweightObjectRef toLightweightObjectRef(ObjectReferenceType ref) {
return ref != null ? new LightweightObjectRefImpl(ref) : null;
}
//endregion
}