/*
* Copyright (c) 2010-2017 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.model.impl.lens.projector;
import java.util.*;
import java.util.Map.Entry;
import java.util.Objects;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import com.evolveum.midpoint.common.ActivationComputer;
import com.evolveum.midpoint.model.api.ModelExecuteOptions;
import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision;
import com.evolveum.midpoint.model.common.SystemObjectCache;
import com.evolveum.midpoint.model.common.expression.ObjectDeltaObject;
import com.evolveum.midpoint.model.common.mapping.Mapping;
import com.evolveum.midpoint.model.common.mapping.MappingFactory;
import com.evolveum.midpoint.model.impl.controller.ModelUtils;
import com.evolveum.midpoint.model.impl.lens.AbstractConstruction;
import com.evolveum.midpoint.model.impl.lens.AssignmentEvaluator;
import com.evolveum.midpoint.model.impl.lens.Construction;
import com.evolveum.midpoint.model.impl.lens.ConstructionPack;
import com.evolveum.midpoint.model.impl.lens.EvaluatedAssignmentImpl;
import com.evolveum.midpoint.model.impl.lens.ItemValueWithOrigin;
import com.evolveum.midpoint.model.impl.lens.LensContext;
import com.evolveum.midpoint.model.impl.lens.LensFocusContext;
import com.evolveum.midpoint.model.impl.lens.LensProjectionContext;
import com.evolveum.midpoint.model.impl.lens.LensUtil;
import com.evolveum.midpoint.prism.ItemDefinition;
import com.evolveum.midpoint.prism.PrismContainerDefinition;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismPropertyValue;
import com.evolveum.midpoint.prism.PrismReference;
import com.evolveum.midpoint.prism.PrismReferenceDefinition;
import com.evolveum.midpoint.prism.PrismReferenceValue;
import com.evolveum.midpoint.prism.PrismValue;
import com.evolveum.midpoint.prism.delta.ContainerDelta;
import com.evolveum.midpoint.prism.delta.DeltaMapTriple;
import com.evolveum.midpoint.prism.delta.DeltaSetTriple;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.delta.PlusMinusZero;
import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple;
import com.evolveum.midpoint.prism.delta.ReferenceDelta;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.provisioning.api.ProvisioningService;
import com.evolveum.midpoint.repo.api.RepositoryService;
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.result.OperationResultStatus;
import com.evolveum.midpoint.schema.util.FocusTypeUtil;
import com.evolveum.midpoint.schema.util.ObjectResolver;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
import com.evolveum.midpoint.schema.util.SchemaDebugUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ConfigurationException;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.PolicyViolationException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
/**
* Assignment processor is recomputing user assignments. It recomputes all the assignments whether they are direct
* or indirect (roles).
*
* Processor does not do the complete recompute. Only the account "existence" is recomputed. I.e. the processor determines
* what accounts should be added, deleted or kept as they are. The result is marked in account context SynchronizationPolicyDecision.
* This step does not create any deltas. It recomputes the attributes to delta set triples but does not "refine" them to deltas yet.
* It cannot create deltas as other mapping may interfere, e.g. outbound mappings. These needs to be computed before we can
* create the final deltas (because there may be mapping exclusions, interference of weak mappings, etc.)
*
* The result of assignment processor are intermediary data in the context such as LensContext.evaluatedAssignmentTriple and
* LensProjectionContext.accountConstructionDeltaSetTriple.
*
* @author Radovan Semancik
*/
@Component
public class AssignmentProcessor {
@Autowired
@Qualifier("cacheRepositoryService")
private RepositoryService repositoryService;
@Autowired
private ObjectResolver objectResolver;
@Autowired
private SystemObjectCache systemObjectCache;
@Autowired
private PrismContext prismContext;
@Autowired
private MappingFactory mappingFactory;
@Autowired
private MappingEvaluator mappingEvaluator;
@Autowired(required=true)
private ActivationComputer activationComputer;
@Autowired(required=true)
private ProvisioningService provisioningService;
@Autowired(required=true)
private ConstructionProcessor constructionProcessor;
@Autowired
private ObjectTemplateProcessor objectTemplateProcessor;
@Autowired
private PolicyRuleProcessor policyRuleProcessor;
private static final Trace LOGGER = TraceManager.getTrace(AssignmentProcessor.class);
/**
* Processing all the assignments to determine which projections should be added, deleted or kept as they are.
* Generic method for all projection types (theoretically).
*/
@SuppressWarnings("unchecked")
public <O extends ObjectType> void processAssignmentsProjections(LensContext<O> context, XMLGregorianCalendar now,
Task task, OperationResult parentResult) throws SchemaException,
ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException {
LensFocusContext<O> focusContext = context.getFocusContext();
if (focusContext == null) {
return;
}
if (!FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) {
// We can do this only for FocusType.
return;
}
// if (ModelExecuteOptions.isLimitPropagation(context.getOptions()) && SchemaConstants.CHANGE_CHANNEL_DISCOVERY.equals(QNameUtil.uriToQName(context.getChannel()))){
// //do not execute assignment if the execution was triggered by compensation mechanism and limitPropagation is set
// return;
// }
OperationResult result = parentResult.createSubresult(AssignmentProcessor.class.getName() + ".processAssignmentsProjections");
try {
processAssignmentsProjectionsWithFocus((LensContext<? extends FocusType>)context, now, task, result);
} catch (SchemaException | ObjectNotFoundException | ExpressionEvaluationException | PolicyViolationException |
CommunicationException | ConfigurationException | SecurityViolationException | RuntimeException | Error e) {
result.recordFatalError(e);
throw e;
}
OperationResultStatus finalStatus = OperationResultStatus.SUCCESS;
String message = null;
int errors = 0;
for (OperationResult subresult: result.getSubresults()) {
if (subresult.isError()) {
errors++;
if (message == null) {
message = subresult.getMessage();
} else {
message = errors + " errors";
}
finalStatus = OperationResultStatus.PARTIAL_ERROR;
}
}
result.setStatus(finalStatus);
result.setMessage(message);
result.cleanupResult();
}
/**
* Processing focus-projection assignments (including roles).
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private <F extends FocusType> void processAssignmentsProjectionsWithFocus(LensContext<F> context, XMLGregorianCalendar now,
Task task, OperationResult result) throws SchemaException,
ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException {
// PREPARE ASSIGNMENT DELTA
LensFocusContext<F> focusContext = context.getFocusContext();
ObjectDelta<F> focusDelta = focusContext.getDelta();
if (focusDelta != null && focusDelta.isDelete()) {
processFocusDelete(context, result);
return;
}
checkAssignmentDeltaSanity(context);
// ASSIGNMENT EVALUATION
// Initializing assignment evaluator. This will be used later to process all the assignments including the nested
// assignments (roles).
AssignmentEvaluator<F> assignmentEvaluator = createAssignmentEvaluator(context, now);
ObjectType source = determineSource(focusContext);
AssignmentTripleEvaluator<F> assignmentTripleEvaluator = new AssignmentTripleEvaluator<>();
assignmentTripleEvaluator.setActivationComputer(activationComputer);
assignmentTripleEvaluator.setAssignmentEvaluator(assignmentEvaluator);
assignmentTripleEvaluator.setContext(context);
assignmentTripleEvaluator.setNow(now);
assignmentTripleEvaluator.setPrismContext(prismContext);
assignmentTripleEvaluator.setResult(result);
assignmentTripleEvaluator.setSource(source);
assignmentTripleEvaluator.setTask(task);
// Normal processing. The enforcement policy requires that assigned accounts should be added, so we need to figure out
// which assignments were added. Do a complete recompute for all the enforcement modes. We can do that because this does
// not create deltas, it just creates the triples. So we can decide what to do later when we convert triples to deltas.
// Evaluates all assignments and sorts them to triple: added, removed and untouched assignments.
// This is where most of the assignment-level action happens.
DeltaSetTriple<EvaluatedAssignmentImpl<F>> evaluatedAssignmentTriple = assignmentTripleEvaluator.processAllAssignments();
policyRuleProcessor.addGlobalPoliciesToAssignments(context, evaluatedAssignmentTriple, task, result);
context.setEvaluatedAssignmentTriple((DeltaSetTriple)evaluatedAssignmentTriple);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("evaluatedAssignmentTriple:\n{}", evaluatedAssignmentTriple.debugDump());
}
// PROCESSING POLICIES
policyRuleProcessor.processPolicies(context, evaluatedAssignmentTriple, result);
boolean needToReevaluateAssignments = policyRuleProcessor.processPruning(context, evaluatedAssignmentTriple, result);
if (needToReevaluateAssignments) {
LOGGER.debug("Re-evaluating assignments because exclusion pruning rule was triggered");
evaluatedAssignmentTriple = assignmentTripleEvaluator.processAllAssignments();
context.setEvaluatedAssignmentTriple((DeltaSetTriple)evaluatedAssignmentTriple);
policyRuleProcessor.addGlobalPoliciesToAssignments(context, evaluatedAssignmentTriple, task, result);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("re-evaluatedAssignmentTriple:\n{}", evaluatedAssignmentTriple.debugDump());
}
policyRuleProcessor.processPolicies(context, evaluatedAssignmentTriple, result);
}
//policyRuleProcessor.storeAssignmentPolicySituation(context, evaluatedAssignmentTriple, result);
// PROCESSING FOCUS
Map<ItemPath,DeltaSetTriple<? extends ItemValueWithOrigin<?,?>>> focusOutputTripleMap = new HashMap<>();
collectFocusTripleFromMappings(evaluatedAssignmentTriple.getPlusSet(), focusOutputTripleMap, PlusMinusZero.PLUS);
collectFocusTripleFromMappings(evaluatedAssignmentTriple.getMinusSet(), focusOutputTripleMap, PlusMinusZero.MINUS);
collectFocusTripleFromMappings(evaluatedAssignmentTriple.getZeroSet(), focusOutputTripleMap, PlusMinusZero.ZERO);
ObjectDeltaObject<F> focusOdo = focusContext.getObjectDeltaObject();
Collection<ItemDelta<?,?>> focusDeltas = objectTemplateProcessor.computeItemDeltas(focusOutputTripleMap, null,
focusOdo.getObjectDelta(), focusOdo.getNewObject(), focusContext.getObjectDefinition(),
"focus mappings in assignments of "+focusContext.getHumanReadableName());
LOGGER.trace("Computed focus deltas: {}", focusDeltas);
focusContext.applyProjectionWaveSecondaryDeltas(focusDeltas);
focusContext.recompute();
// PROCESSING PROJECTIONS
// Evaluate the constructions in assignements now. These were not evaluated in the first pass of AssignmentEvaluator
// because there may be interaction from focusMappings of some roles to outbound mappings of other roles.
// Now we have complete focus with all the focusMappings so we can evaluate the constructions
evaluateConstructions(context, evaluatedAssignmentTriple, task, result);
ComplexConstructionConsumer<ResourceShadowDiscriminator,Construction<F>> consumer = new ComplexConstructionConsumer<ResourceShadowDiscriminator,Construction<F>>() {
private boolean processOnlyExistingProjCxts;
@Override
public boolean before(ResourceShadowDiscriminator rat) {
if (rat.getResourceOid() == null) {
throw new IllegalStateException("Resource OID null in ResourceAccountType during assignment processing");
}
if (rat.getIntent() == null) {
throw new IllegalStateException("Account type is null in ResourceAccountType during assignment processing");
}
processOnlyExistingProjCxts = false;
if (ModelExecuteOptions.isLimitPropagation(context.getOptions())){
if (context.getTriggeredResourceOid() != null
&& !rat.getResourceOid().equals(context.getTriggeredResourceOid())) {
LOGGER.trace(
"Skipping processing construction for shadow identified by {} because of limitation to propagate changes only for resource {}",
rat, context.getTriggeredResourceOid());
return false;
}
if (SchemaConstants.CHANGE_CHANNEL_DISCOVERY.equals(QNameUtil.uriToQName(context.getChannel()))) {
LOGGER.trace("Processing of shadow identified by {} will be skipped because of limitation for discovery channel."); // TODO is this message OK? [med]
processOnlyExistingProjCxts = true;
}
}
return true;
}
@Override
public void onAssigned(ResourceShadowDiscriminator rat, String desc) {
LensProjectionContext projectionContext = LensUtil.getOrCreateProjectionContext(context, rat);
projectionContext.setAssigned(true);
projectionContext.setAssignedOld(false);
projectionContext.setLegalOld(false);
AssignmentPolicyEnforcementType assignmentPolicyEnforcement = projectionContext.getAssignmentPolicyEnforcementType();
if (assignmentPolicyEnforcement != AssignmentPolicyEnforcementType.NONE) {
LOGGER.trace("Projection {} legal: assigned (valid)", desc);
projectionContext.setLegal(true);
}
}
@Override
public void onUnchangedValid(ResourceShadowDiscriminator key, String desc) {
LensProjectionContext projectionContext = context.findProjectionContext(key);
if (projectionContext == null) {
if (processOnlyExistingProjCxts){
return;
}
// The projection should exist before the change but it does not
// This happens during reconciliation if there is an inconsistency.
// Pretend that the assignment was just added. That should do.
projectionContext = LensUtil.getOrCreateProjectionContext(context, key);
}
LOGGER.trace("Projection {} legal: unchanged (valid)", desc);
projectionContext.setLegal(true);
projectionContext.setLegalOld(true);
projectionContext.setAssigned(true);
projectionContext.setAssignedOld(true);
}
@Override
public void onUnchangedInvalid(ResourceShadowDiscriminator rat, String desc) {
LensProjectionContext projectionContext = context.findProjectionContext(rat);
if (projectionContext == null) {
if (processOnlyExistingProjCxts){
return;
}
// The projection should exist before the change but it does not
// This happens during reconciliation if there is an inconsistency.
// Pretend that the assignment was just added. That should do.
projectionContext = LensUtil.getOrCreateProjectionContext(context, rat);
}
LOGGER.trace("Projection {} illegal: unchanged (invalid)", desc);
projectionContext.setLegal(false);
projectionContext.setLegalOld(false);
projectionContext.setAssigned(false);
projectionContext.setAssignedOld(false);
}
@Override
public void onUnassigned(ResourceShadowDiscriminator rat, String desc) {
if (accountExists(context,rat)) {
LensProjectionContext projectionContext = context.findProjectionContext(rat);
if (projectionContext == null){
if (processOnlyExistingProjCxts){
return;
}
projectionContext = LensUtil.getOrCreateProjectionContext(context, rat);
}
projectionContext.setAssigned(false);
projectionContext.setAssignedOld(true);
projectionContext.setLegalOld(true);
AssignmentPolicyEnforcementType assignmentPolicyEnforcement = projectionContext.getAssignmentPolicyEnforcementType();
// TODO: check for MARK and LEGALIZE enforcement policies ....add delete laso for relative enforcemenet
if (assignmentPolicyEnforcement == AssignmentPolicyEnforcementType.FULL
|| assignmentPolicyEnforcement == AssignmentPolicyEnforcementType.RELATIVE) {
LOGGER.trace("Projection {} illegal: unassigned", desc);
projectionContext.setLegal(false);
} else {
LOGGER.trace("Projection {} legal: unassigned, but allowed by policy ({})", desc, assignmentPolicyEnforcement);
projectionContext.setLegal(true);
}
} else {
LOGGER.trace("Projection {} nothing: unassigned (valid->invalid) but not there", desc);
// We have to delete something that is not there. Nothing to do.
}
}
@Override
public void after(ResourceShadowDiscriminator rat, String desc,
DeltaMapTriple<ResourceShadowDiscriminator, ConstructionPack<Construction<F>>> constructionMapTriple) {
PrismValueDeltaSetTriple<PrismPropertyValue<Construction>> projectionConstructionDeltaSetTriple =
new PrismValueDeltaSetTriple<>(
getConstructions(constructionMapTriple.getZeroMap().get(rat), true),
getConstructions(constructionMapTriple.getPlusMap().get(rat), true),
getConstructions(constructionMapTriple.getMinusMap().get(rat), false));
LensProjectionContext projectionContext = context.findProjectionContext(rat);
if (projectionContext != null) {
// This can be null in a exotic case if we delete already deleted account
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Construction delta set triple for {}:\n{}", rat,
projectionConstructionDeltaSetTriple.debugDump(1));
}
projectionContext.setConstructionDeltaSetTriple(projectionConstructionDeltaSetTriple);
if (isForceRecon(constructionMapTriple.getZeroMap().get(rat)) || isForceRecon(constructionMapTriple.getPlusMap().get(rat)) || isForceRecon(constructionMapTriple.getMinusMap().get(rat))) {
projectionContext.setDoReconciliation(true);
}
}
}
};
constructionProcessor.processConstructions(context, evaluatedAssignmentTriple,
evaluatedAssignment -> evaluatedAssignment.getConstructionTriple(),
construction -> getConstructionMapKey(context, construction, task, result),
consumer,
task, result);
removeIgnoredContexts(context);
finishLegalDecisions(context);
}
private <F extends FocusType> ResourceShadowDiscriminator getConstructionMapKey(LensContext<F> context, Construction<F> construction, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, SecurityViolationException {
String resourceOid = construction.getResource(task, result).getOid();
String intent = construction.getIntent();
ShadowKindType kind = construction.getKind();
ResourceType resource = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result);
intent = LensUtil.refineProjectionIntent(kind, intent, resource, prismContext);
ResourceShadowDiscriminator rat = new ResourceShadowDiscriminator(resourceOid, kind, intent);
return rat;
}
/**
* Checks if we do not try to modify assignment.targetRef or assignment.construction.kind or intent.
*
* @param context
* @param <F>
* @throws SchemaException
*/
private <F extends FocusType> void checkAssignmentDeltaSanity(LensContext<F> context) throws SchemaException {
ObjectDelta<F> focusDelta = context.getFocusContext().getDelta();
if (focusDelta == null || !focusDelta.isModify() || focusDelta.getModifications() == null) {
return;
}
final ItemPath TARGET_REF_PATH = new ItemPath(FocusType.F_ASSIGNMENT, AssignmentType.F_TARGET_REF);
final ItemPath CONSTRUCTION_KIND_PATH = new ItemPath(FocusType.F_ASSIGNMENT, AssignmentType.F_CONSTRUCTION, ConstructionType.F_KIND);
final ItemPath CONSTRUCTION_INTENT_PATH = new ItemPath(FocusType.F_ASSIGNMENT, AssignmentType.F_CONSTRUCTION, ConstructionType.F_INTENT);
for (@SuppressWarnings("rawtypes") ItemDelta itemDelta : focusDelta.getModifications()) {
ItemPath itemPath = itemDelta.getPath().namedSegmentsOnly();
if (TARGET_REF_PATH.isSubPathOrEquivalent(itemPath)) {
throw new SchemaException("It is not allowed to change targetRef in an assignment. Offending path: " + itemPath);
}
if (CONSTRUCTION_KIND_PATH.isSubPathOrEquivalent(itemPath)) {
throw new SchemaException("It is not allowed to change construction.kind in an assignment. Offending path: " + itemPath);
}
if (CONSTRUCTION_INTENT_PATH.isSubPathOrEquivalent(itemPath)) {
throw new SchemaException("It is not allowed to change construction.intent in an assignment. Offending path: " + itemPath);
}
// TODO some mechanism to detect changing kind/intent by add/delete/replace whole ConstructionType (should be implemented in the caller)
}
}
private <F extends ObjectType> ObjectType determineSource(LensFocusContext<F> focusContext)
throws SchemaException {
ObjectDelta<F> delta = focusContext.getWaveDelta(focusContext.getLensContext().getExecutionWave());
if (delta != null && !delta.isEmpty()) {
return focusContext.getObjectNew().asObjectable();
}
if (focusContext.getObjectCurrent() != null) {
return focusContext.getObjectCurrent().asObjectable();
}
return focusContext.getObjectNew().asObjectable();
}
private <F extends FocusType> void evaluateConstructions(LensContext<F> context,
DeltaSetTriple<EvaluatedAssignmentImpl<F>> evaluatedAssignmentTriple, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException {
evaluateConstructions(context, evaluatedAssignmentTriple.getZeroSet(), task, result);
evaluateConstructions(context, evaluatedAssignmentTriple.getPlusSet(), task, result);
evaluateConstructions(context, evaluatedAssignmentTriple.getMinusSet(), task, result);
}
private <F extends FocusType> void evaluateConstructions(LensContext<F> context,
Collection<EvaluatedAssignmentImpl<F>> evaluatedAssignments, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException {
if (evaluatedAssignments == null) {
return;
}
ObjectDeltaObject<F> focusOdo = null;
LensFocusContext<F> focusContext = context.getFocusContext();
if (focusContext != null) {
focusOdo = focusContext.getObjectDeltaObject();
}
Iterator<EvaluatedAssignmentImpl<F>> iterator = evaluatedAssignments.iterator();
while (iterator.hasNext()) {
EvaluatedAssignmentImpl<F> evaluatedAssignment = iterator.next();
try {
evaluatedAssignment.evaluateConstructions(focusOdo, context.getSystemConfiguration(), task, result);
} catch (ObjectNotFoundException ex){
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Processing of assignment resulted in error {}: {}", ex, SchemaDebugUtil.prettyPrint(evaluatedAssignment.getAssignmentType()));
}
iterator.remove();
if (!ModelExecuteOptions.isForce(context.getOptions())){
ModelUtils.recordFatalError(result, ex);
}
} catch (SchemaException ex){
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Processing of assignment resulted in error {}: {}", ex, SchemaDebugUtil.prettyPrint(evaluatedAssignment.getAssignmentType()));
}
ModelUtils.recordFatalError(result, ex);
String resourceOid = FocusTypeUtil.determineConstructionResource(evaluatedAssignment.getAssignmentType());
if (resourceOid == null) {
// This is a role assignment or something like that. Just throw the original exception for now.
throw ex;
}
ResourceShadowDiscriminator rad = new ResourceShadowDiscriminator(resourceOid,
FocusTypeUtil.determineConstructionKind(evaluatedAssignment.getAssignmentType()),
FocusTypeUtil.determineConstructionIntent(evaluatedAssignment.getAssignmentType()));
LensProjectionContext accCtx = context.findProjectionContext(rad);
if (accCtx != null) {
accCtx.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.BROKEN);
}
iterator.remove();
}
}
}
/**
* Simply mark all projections as illegal - except those that are being unliked
*/
private <F extends FocusType> void processFocusDelete(LensContext<F> context, OperationResult result) {
for (LensProjectionContext projectionContext: context.getProjectionContexts()) {
if (projectionContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.UNLINK) {
// We do not want to affect unliked projections
continue;
}
projectionContext.setLegal(false);
projectionContext.setLegalOld(true);
}
}
@NotNull
private Collection<PrismPropertyValue<Construction>> getConstructions(ConstructionPack accountConstructionPack, boolean validOnly) {
if (accountConstructionPack == null) {
return Collections.emptySet();
}
if (validOnly && !accountConstructionPack.hasValidAssignment()) {
return Collections.emptySet();
}
return accountConstructionPack.getConstructions();
}
private boolean isForceRecon(ConstructionPack accountConstructionPack) {
if (accountConstructionPack == null) {
return false;
}
return accountConstructionPack.isForceRecon();
}
/**
* Set 'legal' flag for the accounts that does not have it already
*/
private <F extends FocusType> void finishLegalDecisions(LensContext<F> context) throws PolicyViolationException, SchemaException {
for (LensProjectionContext projectionContext: context.getProjectionContexts()) {
if (projectionContext.isLegal() != null) {
// already have decision
propagateLegalDecisionToHigherOrders(context, projectionContext);
continue;
}
String desc = projectionContext.toHumanReadableString();
if (projectionContext.isLegalize()){
LOGGER.trace("Projection {} legal: legalized", desc);
createAssignmentDelta(context, projectionContext);
projectionContext.setAssigned(true);
projectionContext.setAssignedOld(false);
projectionContext.setLegal(true);
projectionContext.setLegalOld(false);
} else {
AssignmentPolicyEnforcementType enforcementType = projectionContext.getAssignmentPolicyEnforcementType();
if (enforcementType == AssignmentPolicyEnforcementType.FULL) {
LOGGER.trace("Projection {} illegal: no assignment in FULL enforcement", desc);
// What is not explicitly allowed is illegal in FULL enforcement mode
projectionContext.setLegal(false);
// We need to set the old value for legal to false. There was no assignment delta for it.
// If it were then the code could not get here.
projectionContext.setLegalOld(false);
if (projectionContext.isAdd()) {
throw new PolicyViolationException("Attempt to add projection "+projectionContext.toHumanReadableString()
+" while the synchronization enforcement policy is FULL and the projection is not assigned");
}
} else if (enforcementType == AssignmentPolicyEnforcementType.NONE && !projectionContext.isThombstone()) {
if (projectionContext.isAdd()) {
LOGGER.trace("Projection {} legal: added in NONE policy", desc);
projectionContext.setLegal(true);
projectionContext.setLegalOld(false);
} else {
if (projectionContext.isExists()) {
LOGGER.trace("Projection {} legal: exists in NONE policy", desc);
} else {
LOGGER.trace("Projection {} illegal: does not exists in NONE policy", desc);
}
// Everything that exists was legal and is legal. Nothing really changes.
projectionContext.setLegal(projectionContext.isExists());
projectionContext.setLegalOld(projectionContext.isExists());
}
} else if (enforcementType == AssignmentPolicyEnforcementType.POSITIVE && !projectionContext.isThombstone()) {
// Everything that is not yet dead is legal in POSITIVE enforcement mode
LOGGER.trace("Projection {} legal: not dead in POSITIVE policy", desc);
projectionContext.setLegal(true);
projectionContext.setLegalOld(true);
} else if (enforcementType == AssignmentPolicyEnforcementType.RELATIVE && !projectionContext.isThombstone() &&
projectionContext.isLegal() == null && projectionContext.isLegalOld() == null) {
// RELATIVE mode and nothing has changed. Maintain status quo. Pretend that it is legal.
LOGGER.trace("Projection {} legal: no change in RELATIVE policy", desc);
projectionContext.setLegal(true);
projectionContext.setLegalOld(true);
}
}
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Finishing legal decision for {}, thombstone {}, enforcement mode {}, legalize {}: {} -> {}",
projectionContext.toHumanReadableString(), projectionContext.isThombstone(),
projectionContext.getAssignmentPolicyEnforcementType(),
projectionContext.isLegalize(), projectionContext.isLegalOld(), projectionContext.isLegal());
}
propagateLegalDecisionToHigherOrders(context, projectionContext);
}
}
private <F extends ObjectType> void propagateLegalDecisionToHigherOrders(
LensContext<F> context, LensProjectionContext refProjCtx) {
ResourceShadowDiscriminator refDiscr = refProjCtx.getResourceShadowDiscriminator();
if (refDiscr == null) {
return;
}
for (LensProjectionContext aProjCtx: context.getProjectionContexts()) {
ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator();
if (aDiscr != null && refDiscr.equivalent(aDiscr) && (refDiscr.getOrder() < aDiscr.getOrder())) {
aProjCtx.setLegal(refProjCtx.isLegal());
aProjCtx.setLegalOld(refProjCtx.isLegalOld());
aProjCtx.setExists(refProjCtx.isExists());
}
}
}
private <F extends FocusType> void createAssignmentDelta(LensContext<F> context, LensProjectionContext accountContext) throws SchemaException{
Class<F> focusClass = context.getFocusClass();
ContainerDelta<AssignmentType> assignmentDelta = ContainerDelta.createDelta(FocusType.F_ASSIGNMENT, focusClass, prismContext);
AssignmentType assignment = new AssignmentType();
ConstructionType constructionType = new ConstructionType();
constructionType.setResourceRef(ObjectTypeUtil.createObjectRef(accountContext.getResource()));
assignment.setConstruction(constructionType);
assignmentDelta.addValueToAdd(assignment.asPrismContainerValue());
PrismContainerDefinition<AssignmentType> containerDefinition = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(focusClass).findContainerDefinition(FocusType.F_ASSIGNMENT);
assignmentDelta.applyDefinition(containerDefinition);
context.getFocusContext().swallowToProjectionWaveSecondaryDelta(assignmentDelta);
}
public <F extends ObjectType> void processOrgAssignments(LensContext<F> context, OperationResult result) throws SchemaException {
LensFocusContext<F> focusContext = context.getFocusContext();
if (focusContext == null) {
return;
}
Collection<PrismReferenceValue> shouldBeParentOrgRefs = new ArrayList<>();
DeltaSetTriple<EvaluatedAssignmentImpl<?>> evaluatedAssignmentTriple = context.getEvaluatedAssignmentTriple();
if (evaluatedAssignmentTriple == null) {
return; // could be if "assignments" step is skipped
}
for (EvaluatedAssignmentImpl<?> evalAssignment : evaluatedAssignmentTriple.getNonNegativeValues()) {
if (evalAssignment.isValid()) {
addReferences(shouldBeParentOrgRefs, evalAssignment.getOrgRefVals());
}
}
setReferences(focusContext, ObjectType.F_PARENT_ORG_REF, shouldBeParentOrgRefs);
}
public <F extends ObjectType> void checkForAssignmentConflicts(LensContext<F> context,
OperationResult result) throws PolicyViolationException {
for(LensProjectionContext projectionContext: context.getProjectionContexts()) {
if (AssignmentPolicyEnforcementType.NONE == projectionContext.getAssignmentPolicyEnforcementType()){
continue;
}
if (projectionContext.isAssigned()) {
ObjectDelta<ShadowType> projectionPrimaryDelta = projectionContext.getPrimaryDelta();
if (projectionPrimaryDelta != null) {
if (projectionPrimaryDelta.isDelete()) {
throw new PolicyViolationException("Attempt to delete "+projectionContext.getHumanReadableName()+" while " +
"it is assigned violates an assignment policy");
}
}
}
}
}
public void processAssignmentsAccountValues(LensProjectionContext accountContext, OperationResult result) throws SchemaException,
ObjectNotFoundException, ExpressionEvaluationException {
// TODO: reevaluate constructions
// This should re-evaluate all the constructions. They are evaluated already, evaluated in the assignment step before.
// But if there is any iteration counter that it will not be taken into account
}
private String dumpAccountMap(Map<ResourceShadowDiscriminator, ConstructionPack> accountMap) {
StringBuilder sb = new StringBuilder();
Set<Entry<ResourceShadowDiscriminator, ConstructionPack>> entrySet = accountMap.entrySet();
Iterator<Entry<ResourceShadowDiscriminator, ConstructionPack>> i = entrySet.iterator();
while (i.hasNext()) {
Entry<ResourceShadowDiscriminator, ConstructionPack> entry = i.next();
sb.append(entry.getKey()).append(": ");
sb.append(entry.getValue());
if (i.hasNext()) {
sb.append("\n");
}
}
return sb.toString();
}
private <F extends ObjectType> boolean accountExists(LensContext<F> context, ResourceShadowDiscriminator rat) {
LensProjectionContext accountSyncContext = context.findProjectionContext(rat);
if (accountSyncContext == null) {
return false;
}
if (accountSyncContext.getObjectCurrent() == null) {
return false;
}
return true;
}
private void markPolicyDecision(LensProjectionContext accountSyncContext, SynchronizationPolicyDecision decision) {
if (accountSyncContext.getSynchronizationPolicyDecision() == null) {
accountSyncContext.setSynchronizationPolicyDecision(decision);
}
}
public <F extends ObjectType> void removeIgnoredContexts(LensContext<F> context) {
Collection<LensProjectionContext> projectionContexts = context.getProjectionContexts();
Iterator<LensProjectionContext> projectionIterator = projectionContexts.iterator();
while (projectionIterator.hasNext()) {
LensProjectionContext projectionContext = projectionIterator.next();
if (projectionContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.IGNORE) {
projectionIterator.remove();
}
}
}
private <V extends PrismValue, D extends ItemDefinition, F extends FocusType> XMLGregorianCalendar collectFocusTripleFromMappings(
Collection<EvaluatedAssignmentImpl<F>> evaluatedAssignments,
Map<ItemPath, DeltaSetTriple<? extends ItemValueWithOrigin<?,?>>> outputTripleMap,
PlusMinusZero plusMinusZero) throws SchemaException {
XMLGregorianCalendar nextRecomputeTime = null;
for (EvaluatedAssignmentImpl<F> ea: evaluatedAssignments) {
Collection<Mapping<V,D>> focusMappings = (Collection)ea.getFocusMappings();
for (Mapping<V,D> mapping: focusMappings) {
ItemPath itemPath = mapping.getOutputPath();
DeltaSetTriple<ItemValueWithOrigin<V,D>> outputTriple = ItemValueWithOrigin.createOutputTriple(mapping);
if (outputTriple == null) {
continue;
}
if (plusMinusZero == PlusMinusZero.PLUS) {
outputTriple.addAllToPlusSet(outputTriple.getZeroSet());
outputTriple.clearZeroSet();
outputTriple.clearMinusSet();
} else if (plusMinusZero == PlusMinusZero.MINUS) {
outputTriple.addAllToMinusSet(outputTriple.getZeroSet());
outputTriple.clearZeroSet();
outputTriple.clearPlusSet();
}
DeltaSetTriple<ItemValueWithOrigin<V,D>> mapTriple = (DeltaSetTriple<ItemValueWithOrigin<V,D>>) outputTripleMap.get(itemPath);
if (mapTriple == null) {
outputTripleMap.put(itemPath, outputTriple);
} else {
mapTriple.merge(outputTriple);
}
}
}
return nextRecomputeTime;
}
public <F extends ObjectType> void processMembershipAndDelegatedRefs(LensContext<F> context,
OperationResult result) throws SchemaException {
LensFocusContext<F> focusContext = context.getFocusContext();
if (focusContext == null || !FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) {
return;
}
Collection<PrismReferenceValue> shouldBeRoleRefs = new ArrayList<>();
Collection<PrismReferenceValue> shouldBeDelegatedRefs = new ArrayList<>();
DeltaSetTriple<EvaluatedAssignmentImpl<?>> evaluatedAssignmentTriple = context.getEvaluatedAssignmentTriple();
if (evaluatedAssignmentTriple == null) {
return; // could be if the "assignments" step is skipped
}
for (EvaluatedAssignmentImpl<?> evalAssignment : evaluatedAssignmentTriple.getNonNegativeValues()) {
if (evalAssignment.isValid()) {
addReferences(shouldBeRoleRefs, evalAssignment.getMembershipRefVals());
addReferences(shouldBeDelegatedRefs, evalAssignment.getDelegationRefVals());
}
}
setReferences(focusContext, FocusType.F_ROLE_MEMBERSHIP_REF, shouldBeRoleRefs);
setReferences(focusContext, FocusType.F_DELEGATED_REF, shouldBeDelegatedRefs);
}
private <F extends ObjectType> void setReferences(LensFocusContext<F> focusContext, QName itemName,
Collection<PrismReferenceValue> targetState) throws SchemaException {
PrismObject<F> focusOld = focusContext.getObjectOld();
if (focusOld == null) {
if (targetState.isEmpty()) {
return;
}
} else {
PrismReference existingState = focusOld.findReference(itemName);
if (existingState == null || existingState.isEmpty()) {
if (targetState.isEmpty()) {
return;
}
} else {
// we don't use QNameUtil.match here, because we want to ensure we store qualified values there
// (and newValues are all qualified)
Comparator<PrismReferenceValue> comparator =
(a, b) -> 2*a.getOid().compareTo(b.getOid())
+ (Objects.equals(a.getRelation(), b.getRelation()) ? 0 : 1);
if (MiscUtil.unorderedCollectionCompare(targetState, existingState.getValues(), comparator)) {
return;
}
}
}
PrismReferenceDefinition itemDef = focusContext.getObjectDefinition().findItemDefinition(itemName, PrismReferenceDefinition.class);
ReferenceDelta itemDelta = new ReferenceDelta(itemName, itemDef, focusContext.getObjectDefinition().getPrismContext());
itemDelta.setValuesToReplace(targetState);
focusContext.swallowToSecondaryDelta(itemDelta);
}
private void addReferences(Collection<PrismReferenceValue> extractedReferences, Collection<PrismReferenceValue> references) {
for (PrismReferenceValue reference: references) {
boolean found = false;
for (PrismReferenceValue exVal: extractedReferences) {
if (exVal.getOid().equals(reference.getOid())
&& ObjectTypeUtil.relationsEquivalent(exVal.getRelation(), reference.getRelation())) {
found = true;
break;
}
}
if (!found) {
PrismReferenceValue ref = reference.clone(false); // using copyObject=false instead of calling canonicalize()
if (ref.getRelation() != null && QNameUtil.isUnqualified(ref.getRelation())) {
ref.setRelation(new QName(SchemaConstants.NS_ORG, ref.getRelation().getLocalPart(), SchemaConstants.PREFIX_NS_ORG));
}
extractedReferences.add(ref);
}
}
}
private <F extends FocusType> AssignmentEvaluator<F> createAssignmentEvaluator(LensContext<F> context,
XMLGregorianCalendar now) throws SchemaException {
return new AssignmentEvaluator.Builder<F>()
.repository(repositoryService)
.focusOdo(context.getFocusContext().getObjectDeltaObject())
.lensContext(context)
.channel(context.getChannel())
.objectResolver(objectResolver)
.systemObjectCache(systemObjectCache)
.prismContext(prismContext)
.mappingFactory(mappingFactory)
.mappingEvaluator(mappingEvaluator)
.activationComputer(activationComputer)
.now(now)
.systemConfiguration(context.getSystemConfiguration())
.build();
}
}