/*
* Copyright (c) 2010-2015 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.primary.assignments;
import com.evolveum.midpoint.model.api.context.ModelContext;
import com.evolveum.midpoint.model.impl.lens.LensContext;
import com.evolveum.midpoint.prism.PrismContainer;
import com.evolveum.midpoint.prism.PrismContainerValue;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.delta.ChangeType;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.path.IdItemPathSegment;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.path.ItemPathSegment;
import com.evolveum.midpoint.schema.DeltaConvertor;
import com.evolveum.midpoint.schema.ObjectTreeDeltas;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
import com.evolveum.midpoint.task.api.Task;
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.itemApproval.ApprovalRequest;
import com.evolveum.midpoint.wf.impl.processes.itemApproval.ItemApprovalProcessInterface;
import com.evolveum.midpoint.wf.impl.processes.itemApproval.ReferenceResolver;
import com.evolveum.midpoint.wf.impl.processes.itemApproval.RelationResolver;
import com.evolveum.midpoint.wf.impl.processes.modifyAssignment.AssignmentModification;
import com.evolveum.midpoint.wf.impl.processors.primary.ModelInvocationContext;
import com.evolveum.midpoint.wf.impl.processors.primary.PcpChildWfTaskCreationInstruction;
import com.evolveum.midpoint.wf.impl.processors.primary.aspect.BasePrimaryChangeAspect;
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.ItemDeltaType;
import org.apache.commons.lang.Validate;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
/**
* Change aspect that manages assignment modification approval.
* It starts one process instance for each assignment change that has to be approved.
*
* T is type of the objects being assigned (AbstractRoleType, ResourceType).
* F is the type of the objects to which assignments are made (UserType, AbstractRoleType).
*
* Assumption: assignment target is never modified
*
* @author mederly
*/
@Component
public abstract class ModifyAssignmentAspect<T extends ObjectType, F extends FocusType> extends BasePrimaryChangeAspect {
private static final Trace LOGGER = TraceManager.getTrace(ModifyAssignmentAspect.class);
@Autowired
protected PrismContext prismContext;
@Autowired
protected ItemApprovalProcessInterface itemApprovalProcessInterface;
//region ------------------------------------------------------------ Things that execute on request arrival
@NotNull
@Override
public List<PcpChildWfTaskCreationInstruction> prepareTasks(@NotNull ObjectTreeDeltas objectTreeDeltas,
ModelInvocationContext ctx, @NotNull OperationResult result) throws SchemaException {
if (!isFocusRelevant(ctx.modelContext) || objectTreeDeltas.getFocusChange() == null) {
return Collections.emptyList();
}
List<ApprovalRequest<AssignmentModification>> approvalRequestList = getApprovalRequests(ctx.modelContext,
ctx.wfConfiguration, objectTreeDeltas.getFocusChange(), ctx.taskFromModel, result);
if (approvalRequestList == null || approvalRequestList.isEmpty()) {
return Collections.emptyList();
}
return prepareJobCreateInstructions(ctx.modelContext, ctx.taskFromModel, result, approvalRequestList);
}
private List<ApprovalRequest<AssignmentModification>> getApprovalRequests(ModelContext<?> modelContext,
WfConfigurationType wfConfigurationType, ObjectDelta<? extends ObjectType> change, Task taskFromModel,
OperationResult result) throws SchemaException {
if (change.getChangeType() != ChangeType.MODIFY) {
return null;
}
PrismObject<F> focusOld = (PrismObject<F>) modelContext.getFocusContext().getObjectOld();
F focusTypeOld = focusOld.asObjectable();
PcpAspectConfigurationType config = primaryChangeAspectHelper.getPcpAspectConfigurationType(wfConfigurationType, this);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Relevant assignments in focus modify delta: ");
}
List<ApprovalRequest<AssignmentModification>> approvalRequestList = new ArrayList<>();
final ItemPath ASSIGNMENT_PATH = new ItemPath(UserType.F_ASSIGNMENT);
PrismContainer<AssignmentType> assignmentsOld = focusOld.findContainer(ASSIGNMENT_PATH);
// deltas sorted by assignment to which they are related
Map<Long,List<ItemDeltaType>> deltasById = new HashMap<>();
Iterator<? extends ItemDelta> deltaIterator = change.getModifications().iterator();
while (deltaIterator.hasNext()) {
ItemDelta delta = deltaIterator.next();
if (!ASSIGNMENT_PATH.isSubPath(delta.getPath())) {
continue;
}
Long id = getAssignmentIdFromDeltaPath(assignmentsOld, delta.getPath()); // id may be null
AssignmentType assignmentType = getAssignmentToBeModified(assignmentsOld, id);
if (isAssignmentRelevant(assignmentType)) {
T target = getAssignmentApprovalTarget(assignmentType, result);
boolean approvalRequired = shouldAssignmentBeApproved(config, target);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace(" - target: {} (approval required = {})", target, approvalRequired);
}
if (approvalRequired) {
addToDeltas(deltasById, assignmentType.getId(), delta);
deltaIterator.remove();
}
}
}
if (!deltasById.isEmpty()) {
for (Map.Entry<Long,List<ItemDeltaType>> entry : deltasById.entrySet()) {
Long id = entry.getKey();
AssignmentType assignmentType = getAssignmentToBeModified(assignmentsOld, id);
AssignmentType aCopy = cloneAndCanonicalizeAssignment(assignmentType);
T target = getAssignmentApprovalTarget(assignmentType, result);
ApprovalRequest approvalRequest = createApprovalRequestForModification(config, aCopy, target, entry.getValue(),
createRelationResolver(target, result), createReferenceResolver(modelContext, taskFromModel, result));
approvalRequestList.add(approvalRequest);
}
}
return approvalRequestList;
}
private void addToDeltas(Map<Long, List<ItemDeltaType>> deltasById, Long id, ItemDelta delta) throws SchemaException {
List<ItemDeltaType> deltas = deltasById.get(id);
if (deltas == null) {
deltas = new ArrayList<>();
deltasById.put(id, deltas);
}
Collection<ItemDeltaType> itemDeltaTypes = DeltaConvertor.toItemDeltaTypes(delta);
deltas.addAll(itemDeltaTypes);
}
// path's first segment is "assignment"
public static Long getAssignmentIdFromDeltaPath(PrismContainer<AssignmentType> assignmentsOld, ItemPath path) throws SchemaException {
assert path.getSegments().size() > 1;
ItemPathSegment idSegment = path.getSegments().get(1);
if (idSegment instanceof IdItemPathSegment) {
return ((IdItemPathSegment) idSegment).getId();
}
// id-less path, e.g. assignment/validFrom -- we try to determine ID from the objectOld.
if (assignmentsOld.size() == 0) {
return null;
} else if (assignmentsOld.size() == 1) {
return assignmentsOld.getValues().get(0).getId();
} else {
throw new SchemaException("Illegal path " + path + ": cannot determine which assignment to modify");
}
}
public static AssignmentType getAssignmentToBeModified(PrismContainer<AssignmentType> assignmentsOld, Long id) {
if (id == null && assignmentsOld.size() == 1) {
return assignmentsOld.getValue().asContainerable();
}
PrismContainerValue<AssignmentType> value = assignmentsOld.getValue(id);
if (value == null) {
throw new IllegalStateException("No assignment value with id " + id + " in user old");
}
return value.asContainerable();
}
private List<PcpChildWfTaskCreationInstruction> prepareJobCreateInstructions(ModelContext<?> modelContext, Task taskFromModel, OperationResult result, List<ApprovalRequest<AssignmentModification>> approvalRequestList) throws SchemaException {
List<PcpChildWfTaskCreationInstruction> instructions = new ArrayList<>();
String focusName = MiscDataUtil.getFocusObjectName(modelContext);
for (ApprovalRequest<AssignmentModification> approvalRequest : approvalRequestList) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Approval request = {}", approvalRequest);
}
AssignmentModification itemToApprove = approvalRequest.getItemToApprove();
T target = (T) itemToApprove.getTarget();
Validate.notNull(target);
String targetName = getTargetDisplayName(target);
String focusOid = MiscDataUtil.getFocusObjectOid(modelContext);
PrismObject<UserType> requester = baseModelInvocationProcessingHelper.getRequester(taskFromModel, result);
String approvalTaskName = "Approve modifying assignment of " + targetName + " to " + focusName;
// create a JobCreateInstruction for a given change processor (primaryChangeProcessor in this case)
PcpChildWfTaskCreationInstruction instruction =
PcpChildWfTaskCreationInstruction.createItemApprovalInstruction(
getChangeProcessor(), approvalTaskName,
approvalRequest.getApprovalSchemaType(), null);
// set some common task/process attributes
instruction.prepareCommonAttributes(this, modelContext, requester);
// prepare and set the delta that has to be approved
ObjectDelta<? extends ObjectType> delta = requestToDelta(modelContext, approvalRequest, focusOid);
instruction.setDeltasToProcess(delta);
instruction.setObjectRef(modelContext, result);
instruction.setTargetRef(ObjectTypeUtil.createObjectRef(target), result);
// set the names of midPoint task and activiti process instance
String andExecuting = instruction.isExecuteApprovedChangeImmediately() ? "and execution " : "";
instruction.setTaskName("Approval " + andExecuting + " of modifying assignment of " + targetName + " to " + focusName);
instruction.setProcessInstanceName("Modifying assignment of " + targetName + " to " + focusName);
// setup general item approval process
itemApprovalProcessInterface.prepareStartInstruction(instruction);
instructions.add(instruction);
}
return instructions;
}
private ObjectDelta<? extends ObjectType> requestToDelta(ModelContext<?> modelContext, ApprovalRequest<AssignmentModification> approvalRequest, String objectOid) throws SchemaException {
List<ItemDelta> modifications = new ArrayList<>();
Class<? extends ObjectType> focusClass = primaryChangeAspectHelper.getFocusClass(modelContext);
for (ItemDeltaType itemDeltaType : approvalRequest.getItemToApprove().getModifications()) {
modifications.add(DeltaConvertor.createItemDelta(itemDeltaType, focusClass, prismContext));
}
return ObjectDelta.createModifyDelta(objectOid, modifications, focusClass, ((LensContext) modelContext).getPrismContext());
}
//endregion
//region ------------------------------------------------------------ Things that execute when item is being approved
//endregion
//region ------------------------------------------------------------ Things to override in concrete aspect classes
// a quick check whether expected focus type (User, Role) matches the actual focus type in current model operation context
protected abstract boolean isFocusRelevant(ModelContext modelContext);
// is the assignment relevant for a given aspect? (e.g. is this an assignment of a role?)
protected abstract boolean isAssignmentRelevant(AssignmentType assignmentType);
// should the given assignment be approved? (typically, does the target object have an approver specified?)
protected abstract boolean shouldAssignmentBeApproved(PcpAspectConfigurationType config, T target);
// before creating a delta for the assignment, it has to be cloned and canonicalized by removing full target object
protected abstract AssignmentType cloneAndCanonicalizeAssignment(AssignmentType a);
// creates an approval requests (e.g. by providing approval schema) for a given assignment and a target
protected abstract ApprovalRequest<AssignmentModification> createApprovalRequestForModification(PcpAspectConfigurationType config,
AssignmentType assignmentType, T target, List<ItemDeltaType> modifications, RelationResolver relationResolver,
ReferenceResolver referenceResolver);
// retrieves the relevant target for a given assignment - a role, an org, or a resource
protected abstract T getAssignmentApprovalTarget(AssignmentType assignmentType, OperationResult result);
// creates name to be displayed in the question form (may be overriden by child objects)
protected String getTargetDisplayName(T target) {
if (target.getName() != null) {
return target.getName().getOrig();
} else {
return target.getOid();
}
}
//endregion
}