/* * 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.entitlements; import com.evolveum.midpoint.model.api.context.ModelContext; import com.evolveum.midpoint.model.api.context.ModelProjectionContext; import com.evolveum.midpoint.model.common.expression.ExpressionVariables; import com.evolveum.midpoint.prism.PrismContainerValue; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.delta.builder.DeltaBuilder; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; import com.evolveum.midpoint.schema.constants.SchemaConstants; 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.ObjectNotFoundException; 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.ApprovalRequestImpl; 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.wf.impl.util.MiscDataUtil; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.apache.commons.lang.Validate; import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Component; import javax.xml.datatype.XMLGregorianCalendar; import java.text.DateFormat; import java.util.*; /** * Aspect for adding associations. * * In current version it treats associations that are DIRECTLY added, i.e. not as a part of an assignment. * * @author mederly */ @Component public class AddAssociationAspect extends BasePrimaryChangeAspect { private static final Trace LOGGER = TraceManager.getTrace(AddAssociationAspect.class); //region ------------------------------------------------------------ Things that execute on request arrival @NotNull @Override public List<PcpChildWfTaskCreationInstruction> prepareTasks(@NotNull ObjectTreeDeltas objectTreeDeltas, ModelInvocationContext ctx, @NotNull OperationResult result) throws SchemaException, ObjectNotFoundException { if (!isFocusRelevant(ctx.modelContext)) { return Collections.emptyList(); } List<ApprovalRequest<AssociationAdditionType>> approvalRequestList = getApprovalRequests(ctx.modelContext, baseConfigurationHelper.getPcpConfiguration(ctx.wfConfiguration), objectTreeDeltas, ctx.taskFromModel, result); if (approvalRequestList == null || approvalRequestList.isEmpty()) { return Collections.emptyList(); } return prepareJobCreateInstructions(ctx.modelContext, ctx.taskFromModel, result, approvalRequestList); } protected boolean isFocusRelevant(ModelContext modelContext) { //return modelContext.getFocusClass() != null && UserType.class.isAssignableFrom(modelContext.getFocusClass()); return true; } private List<ApprovalRequest<AssociationAdditionType>> getApprovalRequests(ModelContext<?> modelContext, PrimaryChangeProcessorConfigurationType wfConfigurationType, ObjectTreeDeltas changes, Task taskFromModel, OperationResult result) { List<ApprovalRequest<AssociationAdditionType>> requests = new ArrayList<>(); PcpAspectConfigurationType config = primaryChangeAspectHelper.getPcpAspectConfigurationType(wfConfigurationType, this); Set<Map.Entry<ResourceShadowDiscriminator, ObjectDelta<ShadowType>>> entries = changes.getProjectionChangeMapEntries(); for (Map.Entry<ResourceShadowDiscriminator, ObjectDelta<ShadowType>> entry : entries) { ObjectDelta<ShadowType> delta = entry.getValue(); if (delta.isAdd()) { requests.addAll(getApprovalRequestsFromShadowAdd(config, entry.getValue(), entry.getKey(), modelContext, taskFromModel, result)); } else if (delta.isModify()) { ModelProjectionContext projectionContext = modelContext.findProjectionContext(entry.getKey()); requests.addAll(getApprovalRequestsFromShadowModify( config, projectionContext.getObjectOld(), entry.getValue(), entry.getKey(), modelContext, taskFromModel, result)); } else { // no-op } } return requests; } private List<ApprovalRequest<AssociationAdditionType>> getApprovalRequestsFromShadowAdd(PcpAspectConfigurationType config, ObjectDelta<ShadowType> change, ResourceShadowDiscriminator rsd, ModelContext<?> modelContext, Task taskFromModel, OperationResult result) { LOGGER.trace("Relevant associations in shadow add delta:"); List<ApprovalRequest<AssociationAdditionType>> approvalRequestList = new ArrayList<>(); ShadowType shadowType = change.getObjectToAdd().asObjectable(); Iterator<ShadowAssociationType> associationIterator = shadowType.getAssociation().iterator(); while (associationIterator.hasNext()) { ShadowAssociationType a = associationIterator.next(); AssociationAdditionType itemToApprove = createItemToApprove(a, rsd); if (isAssociationRelevant(config, itemToApprove, rsd, modelContext, taskFromModel, result)) { approvalRequestList.add(createApprovalRequest(config, itemToApprove, modelContext, taskFromModel, result)); associationIterator.remove(); miscDataUtil.generateProjectionOidIfNeeded(modelContext, shadowType, rsd); } } return approvalRequestList; } private AssociationAdditionType createItemToApprove(ShadowAssociationType a, ResourceShadowDiscriminator rsd) { ShadowAssociationType aCopy = cloneAndCanonicalizeAssociation(a); AssociationAdditionType aat = new AssociationAdditionType(prismContext); aat.setAssociation(aCopy); aat.setResourceShadowDiscriminator(rsd.toResourceShadowDiscriminatorType()); return aat; } private List<ApprovalRequest<AssociationAdditionType>> getApprovalRequestsFromShadowModify(PcpAspectConfigurationType config, PrismObject<ShadowType> shadowOld, ObjectDelta<ShadowType> change, ResourceShadowDiscriminator rsd, ModelContext<?> modelContext, Task taskFromModel, OperationResult result) { LOGGER.trace("Relevant associations in shadow modify delta:"); List<ApprovalRequest<AssociationAdditionType>> approvalRequestList = new ArrayList<>(); Iterator<? extends ItemDelta> deltaIterator = change.getModifications().iterator(); final ItemPath ASSOCIATION_PATH = new ItemPath(ShadowType.F_ASSOCIATION); while (deltaIterator.hasNext()) { ItemDelta delta = deltaIterator.next(); if (!ASSOCIATION_PATH.equivalent(delta.getPath())) { continue; } if (delta.getValuesToAdd() != null && !delta.getValuesToAdd().isEmpty()) { Iterator<PrismContainerValue<ShadowAssociationType>> valueIterator = delta.getValuesToAdd().iterator(); while (valueIterator.hasNext()) { PrismContainerValue<ShadowAssociationType> association = valueIterator.next(); ApprovalRequest<AssociationAdditionType> req = processAssociationToAdd(config, association, rsd, modelContext, taskFromModel, result); if (req != null) { approvalRequestList.add(req); valueIterator.remove(); } } } if (delta.getValuesToReplace() != null && !delta.getValuesToReplace().isEmpty()) { Iterator<PrismContainerValue<ShadowAssociationType>> valueIterator = delta.getValuesToReplace().iterator(); while (valueIterator.hasNext()) { PrismContainerValue<ShadowAssociationType> association = valueIterator.next(); if (existsEquivalentValue(shadowOld, association)) { continue; } ApprovalRequest<AssociationAdditionType> req = processAssociationToAdd(config, association, rsd, 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<ShadowType> shadowOld, PrismContainerValue<ShadowAssociationType> association) { ShadowType shadowType = shadowOld.asObjectable(); for (ShadowAssociationType existing : shadowType.getAssociation()) { if (existing.asPrismContainerValue().equalsRealValue(association)) { // TODO better check return true; } } return false; } private ApprovalRequest<AssociationAdditionType> processAssociationToAdd(PcpAspectConfigurationType config, PrismContainerValue<ShadowAssociationType> associationCval, ResourceShadowDiscriminator rsd, ModelContext<?> modelContext, Task taskFromModel, OperationResult result) { ShadowAssociationType association = associationCval.asContainerable(); AssociationAdditionType itemToApprove = createItemToApprove(association, rsd); if (isAssociationRelevant(config, itemToApprove, rsd, modelContext, taskFromModel, result)) { return createApprovalRequest(config, itemToApprove, modelContext, taskFromModel, result); } else { return null; } } private List<PcpChildWfTaskCreationInstruction> prepareJobCreateInstructions(ModelContext<?> modelContext, Task taskFromModel, OperationResult result, List<ApprovalRequest<AssociationAdditionType>> approvalRequestList) throws SchemaException, ObjectNotFoundException { List<PcpChildWfTaskCreationInstruction> instructions = new ArrayList<>(); String assigneeName = MiscDataUtil.getFocusObjectName(modelContext); String assigneeOid = MiscDataUtil.getFocusObjectOid(modelContext); PrismObject<UserType> requester = baseModelInvocationProcessingHelper.getRequester(taskFromModel, result); for (ApprovalRequest<AssociationAdditionType> approvalRequest : approvalRequestList) { LOGGER.trace("Approval request = {}", approvalRequest); AssociationAdditionType associationAddition = approvalRequest.getItemToApprove(); ShadowAssociationType association = associationAddition.getAssociation(); ShadowType target = getAssociationApprovalTarget(association, result); Validate.notNull(target, "No target in association to be approved"); String targetName = target.getName() != null ? target.getName().getOrig() : "(unnamed)"; String approvalTaskName = "Approve adding " + targetName + " to " + assigneeName; // 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 ObjectTreeDeltas objectTreeDeltas = associationAdditionToDelta(modelContext, associationAddition, assigneeOid); instruction.setDeltasToProcesses(objectTreeDeltas); instruction.setObjectRef(modelContext, result); // TODO - or should we take shadow as an object? 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 adding " + targetName + " to " + assigneeName); instruction.setProcessInstanceName("Adding " + targetName + " to " + assigneeName); // setup general item approval process itemApprovalProcessInterface.prepareStartInstruction(instruction); instructions.add(instruction); } return instructions; } // creates an ObjectTreeDeltas that will be executed after successful approval of the given assignment private ObjectTreeDeltas associationAdditionToDelta(ModelContext<?> modelContext, AssociationAdditionType addition, String objectOid) throws SchemaException { ObjectTreeDeltas changes = new ObjectTreeDeltas(prismContext); ResourceShadowDiscriminator shadowDiscriminator = ResourceShadowDiscriminator.fromResourceShadowDiscriminatorType(addition.getResourceShadowDiscriminator()); String projectionOid = modelContext.findProjectionContext(shadowDiscriminator).getOid(); ObjectDelta<ShadowType> objectDelta = (ObjectDelta<ShadowType>) DeltaBuilder.deltaFor(ShadowType.class, prismContext) .item(ShadowType.F_ASSOCIATION).add(addition.getAssociation().clone()) .asObjectDelta(projectionOid); changes.addProjectionChange(shadowDiscriminator, objectDelta); return changes; } //endregion //region ------------------------------------------------------------ Things that execute when item is being approved private static String formatTime(XMLGregorianCalendar time) { DateFormat formatter = DateFormat.getDateInstance(); return formatter.format(time.toGregorianCalendar().getTime()); } //endregion private boolean isAssociationRelevant(PcpAspectConfigurationType config, AssociationAdditionType itemToApprove, ResourceShadowDiscriminator rsd, ModelContext<?> modelContext, Task task, OperationResult result) { LOGGER.trace(" - considering: {}", itemToApprove); ExpressionVariables variables = new ExpressionVariables(); variables.addVariableDefinition(SchemaConstants.C_ASSOCIATION, itemToApprove.getAssociation()); variables.addVariableDefinition(SchemaConstants.C_SHADOW_DISCRIMINATOR, rsd); boolean applicable = primaryChangeAspectHelper.evaluateApplicabilityCondition( config, modelContext, itemToApprove, variables, this, task, result); LOGGER.trace(" - result: applicable = {}", applicable); return applicable; } private ShadowAssociationType cloneAndCanonicalizeAssociation(ShadowAssociationType a) { return a.clone(); // TODO - should we canonicalize? } // creates an approval requests (e.g. by providing approval schema) for a given assignment and a target private ApprovalRequest<AssociationAdditionType> createApprovalRequest(PcpAspectConfigurationType config, AssociationAdditionType itemToApprove, ModelContext<?> modelContext, Task taskFromModel, OperationResult result) { ApprovalRequest<AssociationAdditionType> request = new ApprovalRequestImpl<>(itemToApprove, config, prismContext); approvalSchemaHelper.prepareSchema(request.getApprovalSchemaType(), createRelationResolver((PrismObject<?>) null, result), // TODO rel resolver createReferenceResolver(modelContext, taskFromModel, result)); return request; } // retrieves the relevant target for a given assignment - a role, an org, or a resource private ShadowType getAssociationApprovalTarget(ShadowAssociationType association, OperationResult result) throws SchemaException, ObjectNotFoundException { return primaryChangeAspectHelper.resolveTargetUnchecked(association, result); } }