/*
* Copyright (c) 2010-2014 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.processors;
import com.evolveum.midpoint.model.api.context.ModelContext;
import com.evolveum.midpoint.model.api.context.ModelProjectionContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.repo.api.RepositoryService;
import com.evolveum.midpoint.schema.ObjectTreeDeltas;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.exception.*;
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.wf.impl.tasks.WfTask;
import com.evolveum.midpoint.wf.impl.tasks.WfTaskController;
import com.evolveum.midpoint.wf.impl.tasks.WfTaskCreationInstruction;
import com.evolveum.midpoint.wf.impl.tasks.WfTaskUtil;
import com.evolveum.midpoint.wf.impl.util.MiscDataUtil;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.WfConfigurationType;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Locale;
/**
* Helper class intended to facilitate processing of model invocation.
* (Currently deals mainly with root job creation.)
*
* Functionality provided here differs from the one in JobController mainly in
* the fact that here we know about binding to model (ModelContext, model operation task),
* and in JobController we do not.
*
* @author mederly
*/
@Component
public class BaseModelInvocationProcessingHelper {
private static final Trace LOGGER = TraceManager.getTrace(BaseModelInvocationProcessingHelper.class);
@Autowired
protected WfTaskController wfTaskController;
@Autowired
private WfTaskUtil wfTaskUtil;
@Autowired
@Qualifier("cacheRepositoryService")
private RepositoryService repositoryService;
/**
* Creates a root job creation instruction.
*
* @param changeProcessor reference to the change processor responsible for the whole operation
* @param modelContext model context in which the original model operation is carried out
* @param taskFromModel task in which the original model operation is carried out
* @param contextForRoot model context that should be put into the root task (might be different from the modelContext)
* @return the job creation instruction
* @throws SchemaException
*/
public WfTaskCreationInstruction createInstructionForRoot(ChangeProcessor changeProcessor, ModelContext modelContext, Task taskFromModel, ModelContext contextForRoot, OperationResult result) throws SchemaException {
WfTaskCreationInstruction instruction;
if (contextForRoot != null) {
instruction = WfTaskCreationInstruction.createModelOnly(changeProcessor, contextForRoot);
} else {
instruction = WfTaskCreationInstruction.createEmpty(changeProcessor);
}
instruction.setTaskName(determineRootTaskName(modelContext));
instruction.setTaskObject(determineRootTaskObject(modelContext));
instruction.setTaskOwner(taskFromModel.getOwner());
instruction.setCreateTaskAsWaiting();
instruction.setRequesterRef(getRequester(taskFromModel, result));
return instruction;
}
/**
* More specific version of the previous method, having contextForRoot equals to modelContext.
*/
public WfTaskCreationInstruction createInstructionForRoot(ChangeProcessor changeProcessor, ModelContext modelContext, Task taskFromModel, OperationResult result) throws SchemaException {
return createInstructionForRoot(changeProcessor, modelContext, taskFromModel, modelContext, result);
}
/**
* Determines the root task name (e.g. "Workflow for adding XYZ (started 1.2.2014 10:34)")
* TODO allow change processor to influence this name
*/
private String determineRootTaskName(ModelContext context) {
String operation;
if (context.getFocusContext() != null && context.getFocusContext().getPrimaryDelta() != null
&& context.getFocusContext().getPrimaryDelta().getChangeType() != null) {
switch (context.getFocusContext().getPrimaryDelta().getChangeType()) {
case ADD: operation = "creation of"; break;
case DELETE: operation = "deletion of"; break;
case MODIFY: operation = "change of"; break;
default: throw new IllegalStateException();
}
} else {
operation = "change of";
}
String name = MiscDataUtil.getFocusObjectName(context);
DateTimeFormatter formatter = DateTimeFormat.forStyle("MM").withLocale(Locale.getDefault());
String time = formatter.print(System.currentTimeMillis());
// DateFormat dateFormat = DateFormat.getDateTimeInstance();
// String time = dateFormat.format(new Date());
return "Approving and executing " + operation + " " + name + " (started " + time + ")";
}
/**
* Determines where to "hang" workflow root task - whether as a subtask of model task, or nowhere (run it as a standalone task).
*/
private Task determineParentTaskForRoot(Task taskFromModel) {
// this is important: if existing task which we have got from model is transient (this is usual case), we create our root task as a task without parent!
// however, if the existing task is persistent (perhaps because the model operation executes already in the context of a workflow), we create a subtask
// todo think heavily about this; there might be a problem if a transient task from model gets (in the future) persistent
// -- in that case, it would not wait for its workflow-related children (but that's its problem, because children could finish even before
// that task is switched to background)
if (taskFromModel.isTransient()) {
return null;
} else {
return taskFromModel;
}
}
/**
* To which object (e.g. user) is the task related?
*/
private PrismObject determineRootTaskObject(ModelContext context) {
PrismObject taskObject = context.getFocusContext().getObjectNew();
if (taskObject != null && taskObject.getOid() == null) {
taskObject = null;
}
return taskObject;
}
/**
* Creates a root job, based on provided job start instruction.
* Puts a reference to the workflow root task to the model task.
*
* @param rootInstruction instruction to use
* @param taskFromModel (potential) parent task
* @param wfConfigurationType
* @param result
* @return reference to a newly created job
* @throws SchemaException
* @throws ObjectNotFoundException
*/
public WfTask submitRootTask(WfTaskCreationInstruction rootInstruction, Task taskFromModel, WfConfigurationType wfConfigurationType,
OperationResult result)
throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException {
WfTask rootWfTask = wfTaskController.submitWfTask(rootInstruction, determineParentTaskForRoot(taskFromModel), wfConfigurationType, taskFromModel.getChannel(), result);
result.setBackgroundTaskOid(rootWfTask.getTask().getOid());
wfTaskUtil.setRootTaskOidImmediate(taskFromModel, rootWfTask.getTask().getOid(), result);
return rootWfTask;
}
public void logJobsBeforeStart(WfTask rootWfTask, OperationResult result) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {
if (!LOGGER.isTraceEnabled()) {
return;
}
StringBuilder sb = new StringBuilder();
sb.append("===[ Situation just before root task starts waiting for subtasks ]===\n");
sb.append("Root job = ").append(rootWfTask).append("; task = ").append(rootWfTask.getTask().debugDump()).append("\n");
if (rootWfTask.hasModelContext()) {
sb.append("Context in root task: \n").append(rootWfTask.retrieveModelContext(result).debugDump(1)).append("\n");
}
List<WfTask> children = rootWfTask.listChildren(result);
for (int i = 0; i < children.size(); i++) {
WfTask child = children.get(i);
sb.append("Child job #").append(i).append(" = ").append(child).append(", its task:\n").append(child.getTask().debugDump(1));
if (child.hasModelContext()) {
sb.append("Context in child task:\n").append(child.retrieveModelContext(result).debugDump(2));
}
}
LOGGER.trace("\n{}", sb.toString());
LOGGER.trace("Now the root task starts waiting for child tasks");
}
public PrismObject<UserType> getRequester(Task task, OperationResult result) {
if (task.getOwner() == null) {
LOGGER.warn("No requester in task {} -- continuing, but the situation is suspicious.", task);
return null;
}
// let's get fresh data (not the ones read on user login)
PrismObject<UserType> requester;
try {
requester = repositoryService.getObject(UserType.class, task.getOwner().getOid(), null, result);
} catch (ObjectNotFoundException e) {
LoggingUtils.logException(LOGGER, "Couldn't get data about task requester (" + task.getOwner() + "), because it does not exist in repository anymore. Using cached data.", e);
requester = task.getOwner().clone();
} catch (SchemaException e) {
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't get data about task requester (" + task.getOwner() + "), due to schema exception. Using cached data.", e);
requester = task.getOwner().clone();
}
return requester;
}
public ObjectTreeDeltas extractTreeDeltasFromModelContext(ModelContext<?> modelContext) {
ObjectTreeDeltas objectTreeDeltas = new ObjectTreeDeltas(modelContext.getPrismContext());
if (modelContext.getFocusContext() != null && modelContext.getFocusContext().getPrimaryDelta() != null) {
objectTreeDeltas.setFocusChange(modelContext.getFocusContext().getPrimaryDelta().clone());
}
for (ModelProjectionContext projectionContext : modelContext.getProjectionContexts()) {
if (projectionContext.getPrimaryDelta() != null) {
objectTreeDeltas.addProjectionChange(projectionContext.getResourceShadowDiscriminator(), projectionContext.getPrimaryDelta());
}
}
return objectTreeDeltas;
}
}