/*
* 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.prism.PrismContainerDefinition;
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.ContainerDelta;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.schema.result.OperationResult;
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.ItemApprovalSpecificContent;
import com.evolveum.midpoint.schema.ObjectTreeDeltas;
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.xml.ns._public.common.common_3.*;
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.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import static com.evolveum.midpoint.schema.util.ObjectTypeUtil.createObjectRef;
import static com.evolveum.midpoint.wf.impl.util.MiscDataUtil.getFocusObjectName;
import static com.evolveum.midpoint.wf.impl.util.MiscDataUtil.getFocusObjectOid;
/**
* Aspect for adding assignments of any type (abstract role or resource).
*
* T is type of the objects being assigned (AbstractRoleType, ResourceType).
* F is the type of the objects to which assignments are made (UserType, AbstractRoleType).
*
* @author mederly
*/
@Component
public abstract class AddAssignmentAspect<T extends ObjectType, F extends FocusType> extends BasePrimaryChangeAspect {
private static final Trace LOGGER = TraceManager.getTrace(AddAssignmentAspect.class);
@Autowired
protected PrismContext prismContext;
@Autowired
protected ItemApprovalProcessInterface itemApprovalProcessInterface;
@Autowired
protected AssignmentHelper assignmentHelper;
//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<AssignmentType>> approvalRequestList = getApprovalRequests(ctx.modelContext,
baseConfigurationHelper.getPcpConfiguration(ctx.wfConfiguration), objectTreeDeltas.getFocusChange(), ctx.taskFromModel, result);
if (approvalRequestList == null || approvalRequestList.isEmpty()) {
return Collections.emptyList();
}
return prepareTaskInstructions(ctx.modelContext, ctx.taskFromModel, result, approvalRequestList);
}
private List<ApprovalRequest<AssignmentType>> getApprovalRequests(ModelContext<?> modelContext,
PrimaryChangeProcessorConfigurationType wfConfigurationType, ObjectDelta<? extends ObjectType> change,
Task taskFromModel, OperationResult result) {
if (change.getChangeType() != ChangeType.ADD && change.getChangeType() != ChangeType.MODIFY) {
return null;
}
PcpAspectConfigurationType config = primaryChangeAspectHelper.getPcpAspectConfigurationType(wfConfigurationType, this);
if (change.getChangeType() == ChangeType.ADD) {
return getApprovalRequestsFromFocusAdd(config, change, modelContext, taskFromModel, result);
} else {
return getApprovalRequestsFromFocusModify(config, modelContext.getFocusContext().getObjectOld(), change, modelContext, taskFromModel, result);
}
}
private List<ApprovalRequest<AssignmentType>> getApprovalRequestsFromFocusAdd(PcpAspectConfigurationType config,
ObjectDelta<? extends ObjectType> change,
ModelContext<?> modelContext, Task taskFromModel, OperationResult result) {
LOGGER.trace("Relevant assignments in focus add delta:");
List<ApprovalRequest<AssignmentType>> approvalRequestList = new ArrayList<>();
FocusType focusType = (FocusType) change.getObjectToAdd().asObjectable();
Iterator<AssignmentType> assignmentTypeIterator = focusType.getAssignment().iterator();
while (assignmentTypeIterator.hasNext()) {
AssignmentType a = assignmentTypeIterator.next();
if (isAssignmentRelevant(a)) {
T specificObjectType = getAssignmentApprovalTarget(a, result);
boolean approvalRequired = shouldAssignmentBeApproved(config, specificObjectType);
LOGGER.trace(" - {} (approval required = {})", specificObjectType, approvalRequired);
if (approvalRequired) {
AssignmentType aCopy = cloneAndCanonicalizeAssignment(a);
approvalRequestList.add(createApprovalRequest(config, aCopy, specificObjectType, modelContext, taskFromModel, result));
assignmentTypeIterator.remove();
miscDataUtil.generateFocusOidIfNeeded(modelContext, change);
}
}
}
return approvalRequestList;
}
private List<ApprovalRequest<AssignmentType>> getApprovalRequestsFromFocusModify(PcpAspectConfigurationType config,
PrismObject<?> focusOld,
ObjectDelta<? extends ObjectType> change, ModelContext<?> modelContext, Task taskFromModel, OperationResult result) {
LOGGER.trace("Relevant assignments in focus modify delta:");
List<ApprovalRequest<AssignmentType>> approvalRequestList = new ArrayList<>();
Iterator<? extends ItemDelta> deltaIterator = change.getModifications().iterator();
final ItemPath ASSIGNMENT_PATH = new ItemPath(FocusType.F_ASSIGNMENT);
while (deltaIterator.hasNext()) {
ItemDelta delta = deltaIterator.next();
if (!ASSIGNMENT_PATH.equivalent(delta.getPath())) {
continue;
}
if (delta.getValuesToAdd() != null && !delta.getValuesToAdd().isEmpty()) {
Iterator<PrismContainerValue<AssignmentType>> valueIterator = delta.getValuesToAdd().iterator();
while (valueIterator.hasNext()) {
PrismContainerValue<AssignmentType> assignmentValue = valueIterator.next();
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Assignment to add = {}", assignmentValue.debugDump());
}
ApprovalRequest<AssignmentType> req = processAssignmentToAdd(config, assignmentValue, modelContext, taskFromModel, result);
if (req != null) {
approvalRequestList.add(req);
valueIterator.remove();
}
}
}
if (delta.getValuesToReplace() != null && !delta.getValuesToReplace().isEmpty()) {
Iterator<PrismContainerValue<AssignmentType>> valueIterator = delta.getValuesToReplace().iterator();
while (valueIterator.hasNext()) {
PrismContainerValue<AssignmentType> assignmentValue = valueIterator.next();
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Assignment to replace = {}", assignmentValue.debugDump());
}
if (existsEquivalentValue(focusOld, assignmentValue)) {
continue;
}
ApprovalRequest<AssignmentType> req = processAssignmentToAdd(config, assignmentValue, modelContext,
taskFromModel, result);
if (req != null) {
approvalRequestList.add(req);
valueIterator.remove();
}
}
}
// let's sanitize the delta
if (delta.getValuesToAdd() != null && delta.getValuesToAdd().isEmpty()) { // empty set of values to add is an illegal state
delta.resetValuesToAdd();
}
if (delta.getValuesToAdd() == null && delta.getValuesToReplace() == null && delta.getValuesToDelete() == null) {
deltaIterator.remove();
}
}
return approvalRequestList;
}
private boolean existsEquivalentValue(PrismObject<?> focusOld, PrismContainerValue<AssignmentType> assignmentValue) {
FocusType focusType = (FocusType) focusOld.asObjectable();
for (AssignmentType existing : focusType.getAssignment()) {
if (existing.asPrismContainerValue().equalsRealValue(assignmentValue)) {
return true;
}
}
return false;
}
private ApprovalRequest<AssignmentType> processAssignmentToAdd(PcpAspectConfigurationType config,
PrismContainerValue<AssignmentType> assignmentCVal, ModelContext<?> modelContext, Task taskFromModel,
OperationResult result) {
AssignmentType assignmentType = assignmentCVal.asContainerable();
if (isAssignmentRelevant(assignmentType)) {
T specificObjectType = getAssignmentApprovalTarget(assignmentType, result);
boolean approvalRequired = shouldAssignmentBeApproved(config, specificObjectType);
LOGGER.trace(" - {} (approval required = {})", specificObjectType, approvalRequired);
if (approvalRequired) {
AssignmentType aCopy = cloneAndCanonicalizeAssignment(assignmentType);
return createApprovalRequest(config, aCopy, specificObjectType, modelContext, taskFromModel, result);
}
}
return null;
}
private List<PcpChildWfTaskCreationInstruction> prepareTaskInstructions(ModelContext<?> modelContext, Task taskFromModel,
OperationResult result, List<ApprovalRequest<AssignmentType>> approvalRequestList) throws SchemaException {
List<PcpChildWfTaskCreationInstruction> instructions = new ArrayList<>();
String assigneeOid = getFocusObjectOid(modelContext);
String assigneeName = getFocusObjectName(modelContext);
PrismObject<UserType> requester = baseModelInvocationProcessingHelper.getRequester(taskFromModel, result);
for (ApprovalRequest<AssignmentType> approvalRequest : approvalRequestList) {
LOGGER.trace("Approval request = {}", approvalRequest);
AssignmentType assignmentType = approvalRequest.getItemToApprove();
T target = getAssignmentApprovalTarget(assignmentType, result);
Validate.notNull(target, "No target in assignment to be approved");
String targetName = target.getName() != null ? target.getName().getOrig() : "(unnamed)";
String approvalTaskName = "Approve adding " + targetName + " to " + assigneeName;
PcpChildWfTaskCreationInstruction<ItemApprovalSpecificContent> instruction =
PcpChildWfTaskCreationInstruction.createItemApprovalInstruction(
getChangeProcessor(), approvalTaskName,
approvalRequest.getApprovalSchemaType(),
null);
instruction.prepareCommonAttributes(this, modelContext, requester);
ObjectDelta<? extends FocusType> delta = assignmentToDelta(modelContext, assignmentType, assigneeOid);
instruction.setDeltasToProcess(delta);
instruction.setObjectRef(modelContext, result);
instruction.setTargetRef(createObjectRef(target), result);
String andExecuting = instruction.isExecuteApprovedChangeImmediately() ? "and execution " : "";
instruction.setTaskName("Approval " + andExecuting + "of assigning " + targetName + " to " + assigneeName);
instruction.setProcessInstanceName("Assigning " + targetName + " to " + assigneeName);
itemApprovalProcessInterface.prepareStartInstruction(instruction);
instructions.add(instruction);
}
return instructions;
}
// creates an ObjectDelta that will be executed after successful approval of the given assignment
@SuppressWarnings("unchecked")
private ObjectDelta<? extends FocusType> assignmentToDelta(ModelContext<?> modelContext, AssignmentType assignmentType, String objectOid) {
PrismObject<FocusType> focus = (PrismObject<FocusType>) modelContext.getFocusContext().getObjectNew();
PrismContainerDefinition<AssignmentType> prismContainerDefinition = focus.getDefinition().findContainerDefinition(FocusType.F_ASSIGNMENT);
ItemDelta<PrismContainerValue<AssignmentType>,PrismContainerDefinition<AssignmentType>> addRoleDelta = new ContainerDelta<>(new ItemPath(), FocusType.F_ASSIGNMENT, prismContainerDefinition, prismContext);
PrismContainerValue<AssignmentType> assignmentValue = assignmentType.asPrismContainerValue().clone();
addRoleDelta.addValueToAdd(assignmentValue);
Class focusClass = primaryChangeAspectHelper.getFocusClass(modelContext);
return ObjectDelta.createModifyDelta(objectOid, addRoleDelta, focusClass, modelContext.getPrismContext());
}
//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<AssignmentType> createApprovalRequest(PcpAspectConfigurationType config,
AssignmentType assignmentType, T target, ModelContext<?> modelContext, Task taskFromModel, OperationResult result);
// 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
}