/*
* Copyright (c) 2010-2016 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.lens.LensContext;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.delta.builder.DeltaBuilder;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.util.CloneUtil;
import com.evolveum.midpoint.provisioning.api.ProvisioningService;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.WfContextUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.task.api.TaskBinding;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.exception.*;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.wf.impl.WfConfiguration;
import com.evolveum.midpoint.wf.impl.processors.ChangeProcessor;
import com.evolveum.midpoint.schema.ObjectTreeDeltas;
import com.evolveum.midpoint.wf.impl.processors.primary.aspect.PrimaryChangeAspect;
import com.evolveum.midpoint.wf.util.ApprovalUtils;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;
import static com.evolveum.midpoint.schema.constants.ObjectTypes.TASK;
import static com.evolveum.midpoint.schema.util.ObjectTypeUtil.createObjectRef;
import static com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType.F_WORKFLOW_CONTEXT;
import static com.evolveum.midpoint.xml.ns._public.common.common_3.WfContextType.*;
import static com.evolveum.midpoint.xml.ns._public.common.common_3.WfPrimaryChangeProcessorStateType.F_DELTAS_TO_PROCESS;
import static com.evolveum.midpoint.xml.ns._public.common.common_3.WfPrimaryChangeProcessorStateType.F_RESULTING_DELTAS;
/**
* Handles low-level task operations.
*
* @author mederly
*/
@Component
public class WfTaskUtil {
@Autowired
private WfConfiguration wfConfiguration;
@Autowired
private PrismContext prismContext;
@Autowired
private ProvisioningService provisioningService;
private static final Trace LOGGER = TraceManager.getTrace(WfTaskUtil.class);
public static final String WAIT_FOR_TASKS_HANDLER_URI = "<<< marker for calling pushWaitForTasksHandlerUri >>>";
public static WfContextType getWorkflowContextChecked(Task task) {
if (task == null) {
throw new IllegalStateException("No task");
} else if (task.getWorkflowContext() == null) {
throw new IllegalStateException("No workflow context in " + task);
} else {
return task.getWorkflowContext();
}
}
@NotNull
public PrimaryChangeAspect getPrimaryChangeAspect(Task task, Collection<PrimaryChangeAspect> aspects) {
WfContextType wfc = getWorkflowContextChecked(task);
WfProcessorSpecificStateType pss = wfc.getProcessorSpecificState();
if (!(pss instanceof WfPrimaryChangeProcessorStateType)) {
throw new IllegalStateException("Expected " + WfPrimaryChangeProcessorStateType.class + " but got " + pss + " in task " + task);
}
WfPrimaryChangeProcessorStateType pcps = ((WfPrimaryChangeProcessorStateType) pss);
String aspectClassName = pcps.getChangeAspect();
if (aspectClassName == null) {
throw new IllegalStateException("No wf primary change aspect defined in task " + task);
}
for (PrimaryChangeAspect a : aspects) {
if (aspectClassName.equals(a.getClass().getName())) {
return a;
}
}
throw new IllegalStateException("Primary change aspect " + aspectClassName + " is not registered.");
}
@NotNull
public ChangeProcessor getChangeProcessor(Task task) {
String processorClassName = task.getWorkflowContext() != null ? task.getWorkflowContext().getChangeProcessor() : null;
if (processorClassName == null) {
throw new IllegalStateException("No change processor defined in task " + task);
}
return wfConfiguration.findChangeProcessor(processorClassName);
}
public String getProcessId(Task task) {
if (task.getWorkflowContext() != null) {
return task.getWorkflowContext().getProcessInstanceId();
} else {
return null;
}
}
public boolean hasModelContext(Task task) {
return task.getModelOperationContext() != null;
}
public ModelContext getModelContext(Task task, OperationResult result) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {
LensContextType modelContextType = task.getModelOperationContext();
if (modelContextType == null) {
return null;
}
return LensContext.fromLensContextType(modelContextType, prismContext, provisioningService, result);
}
public void storeModelContext(Task task, ModelContext context) throws SchemaException {
LensContextType modelContext = context != null ? ((LensContext) context).toLensContextType() : null;
storeModelContext(task, modelContext);
}
public void storeModelContext(Task task, LensContextType context) throws SchemaException {
task.setModelOperationContext(context);
}
public void storeResultingDeltas(ObjectTreeDeltas deltas, Task task) throws SchemaException {
ObjectTreeDeltasType deltasType = ObjectTreeDeltas.toObjectTreeDeltasType(deltas);
if (task.getWorkflowContext().getProcessorSpecificState() == null) {
throw new IllegalStateException("No processor specific state in task " + task);
}
ItemDefinition<?> def = prismContext.getSchemaRegistry()
.findContainerDefinitionByCompileTimeClass(WfPrimaryChangeProcessorStateType.class)
.findPropertyDefinition(F_RESULTING_DELTAS);
ItemPath path = new ItemPath(F_WORKFLOW_CONTEXT, F_PROCESSOR_SPECIFIC_STATE, F_RESULTING_DELTAS);
task.addModification(DeltaBuilder.deltaFor(TaskType.class, prismContext)
.item(path, def).replace(deltasType)
.asItemDelta());
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Stored deltas into task {}:\n{}", task, deltas); // TODO debug dump
}
}
public ObjectTreeDeltas retrieveDeltasToProcess(Task task) throws SchemaException {
PrismProperty<ObjectTreeDeltasType> deltaTypePrismProperty = task.getTaskPrismObject().findProperty(new ItemPath(F_WORKFLOW_CONTEXT, F_PROCESSOR_SPECIFIC_STATE, F_DELTAS_TO_PROCESS));
if (deltaTypePrismProperty == null) {
throw new SchemaException("No deltas to process in task extension; task = " + task);
}
return ObjectTreeDeltas.fromObjectTreeDeltasType(deltaTypePrismProperty.getRealValue(), prismContext);
}
public ObjectTreeDeltas retrieveResultingDeltas(Task task) throws SchemaException {
PrismProperty<ObjectTreeDeltasType> deltaTypePrismProperty = task.getTaskPrismObject().findProperty(new ItemPath(F_WORKFLOW_CONTEXT, F_PROCESSOR_SPECIFIC_STATE, F_RESULTING_DELTAS));
if (deltaTypePrismProperty == null) {
return null;
}
return ObjectTreeDeltas.fromObjectTreeDeltasType(deltaTypePrismProperty.getRealValue(), prismContext);
}
public boolean isProcessInstanceFinished(Task task) {
return task.getWorkflowContext() != null && task.getWorkflowContext().getEndTimestamp() != null;
}
public void setRootTaskOidImmediate(Task task, String oid, OperationResult result) throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException {
Collection<PrismReferenceValue> values = new ArrayList<>();
if (StringUtils.isNotEmpty(oid)) {
values.add(createObjectRef(oid, TASK).asReferenceValue());
}
task.addModificationImmediate(
DeltaBuilder.deltaFor(TaskType.class, prismContext)
.item(F_WORKFLOW_CONTEXT, F_ROOT_TASK_REF).replace(values)
.asItemDelta(),
result);
}
public String getRootTaskOid(Task task) {
ObjectReferenceType ref = task.getWorkflowContext() != null ? task.getWorkflowContext().getRootTaskRef() : null;
return ref != null ? ref.getOid() : null;
}
public void deleteModelOperationContext(Task task) throws SchemaException, ObjectNotFoundException {
task.setModelOperationContext(null);
}
public Collection<ObjectReferenceType> getApprovedByFromTaskTree(Task task, OperationResult result) throws SchemaException {
// we use a OID-keyed map to (1) keep not only the OID, but whole reference, but (2) eliminate uncertainty in comparing references
Map<String,ObjectReferenceType> approvers = new HashMap<>();
List<Task> tasks = task.listSubtasksDeeply(result);
tasks.add(task);
for (Task aTask : tasks) {
List<ObjectReferenceType> approvedBy = getApprovedBy(WfContextUtil.getWorkflowContext(aTask.getTaskPrismObject()));
approvedBy.forEach(ort -> approvers.put(ort.getOid(), ort));
}
return CloneUtil.cloneCollectionMembers(approvers.values()); // to ensure these are parent-less
}
@NotNull
private static List<ObjectReferenceType> getApprovedBy(WfContextType wfc) {
return wfc == null ? Collections.emptyList() : wfc.getEvent().stream()
.flatMap(MiscUtil.instancesOf(WorkItemCompletionEventType.class))
.filter(e -> ApprovalUtils.isApproved(e.getOutput()) && e.getInitiatorRef() != null)
.map(e -> e.getInitiatorRef())
.collect(Collectors.toList());
}
// handlers are stored in the list in the order they should be executed; so the last one has to be pushed first
void pushHandlers(Task task, List<UriStackEntry> handlers) {
for (int i = handlers.size()-1; i >= 0; i--) {
UriStackEntry entry = handlers.get(i);
if (WAIT_FOR_TASKS_HANDLER_URI.equals(entry.getHandlerUri())) {
task.pushWaitForTasksHandlerUri();
} else {
if (!entry.getExtensionDelta().isEmpty()) {
throw new UnsupportedOperationException("handlers with extension delta set are not supported yet");
}
task.pushHandlerUri(entry.getHandlerUri(), entry.getSchedule(), TaskBinding.fromTaskType(entry.getBinding()), (ItemDelta) null);
}
}
}
}