From 1ff58a4eba03df5ed331df6d3c502debc0b8b6e6 Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Wed, 25 Mar 2020 19:06:42 +0100 Subject: [PATCH] Do minor tracing-related improvements 1) Added some auxiliary trace information. 2) Added some experimental data manipulation methods in prism. 3) Fixed PCV.keepPaths method. 4) Fixed serialization of target names in bean-embedded references. 5) Serializing target names in traces. --- .../midpoint/prism/delta/ObjectDelta.java | 7 + .../prism/path/ItemPathCollectionsUtil.java | 10 +- .../prism/impl/PrismContainerValueImpl.java | 4 +- .../prism/impl/delta/ObjectDeltaImpl.java | 44 + .../impl/marshaller/PrismMarshaller.java | 7 +- .../public/common/common-model-context-3.xsd | 220 +- .../model/common/mapping/MappingImpl.java | 20 + .../model/impl/lens/Construction.java | 12 +- .../lens/projector/ActivationProcessor.java | 28 +- .../impl/lens/projector/ContextLoader.java | 3329 +++++++++-------- .../lens/projector/OutboundProcessor.java | 1 + .../ProjectionCredentialsProcessor.java | 4 +- .../projector/focus/InboundProcessor.java | 3 + .../projector/mappings/MappingEvaluator.java | 1 + .../task/quartzimpl/tracing/TracerImpl.java | 4 +- 15 files changed, 2010 insertions(+), 1684 deletions(-) diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java index 4bfbf6528b1..300a651cf86 100644 --- a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/delta/ObjectDelta.java @@ -11,6 +11,7 @@ import com.evolveum.midpoint.prism.equivalence.ParameterizedEquivalenceStrategy; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.util.DebugDumpable; +import com.evolveum.midpoint.util.annotation.Experimental; import com.evolveum.midpoint.util.exception.SchemaException; import org.jetbrains.annotations.NotNull; @@ -299,6 +300,12 @@ static boolean isEmpty(ObjectDelta delta) { boolean isRedundant(PrismObject object, boolean assumeMissingItems) throws SchemaException; + @Experimental + void removeOperationalItems(); + + @Experimental + void removeEstimatedOldValues(); + class FactorOutResultMulti { public final ObjectDelta remainder; public final List> offsprings = new ArrayList<>(); diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/path/ItemPathCollectionsUtil.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/path/ItemPathCollectionsUtil.java index b5b49c82213..2dfe8b6e163 100644 --- a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/path/ItemPathCollectionsUtil.java +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/path/ItemPathCollectionsUtil.java @@ -52,11 +52,11 @@ public static boolean containsSubpath(Collection paths, Item /** * Returns true if the collection contains a superpath of or equivalent path to the given path. * I.e. having collection = { A/B, A/C } - * then the method for this collection and 'path' returns: - * - path = A/B -> true - * - path = A -> true - * - path = A/B/C -> false - * - path = X -> false + * then the method for this collection and 'pathToBeFound' returns: + * - pathToBeFound = A/B -> true + * - pathToBeFound = A -> true + * - pathToBeFound = A/B/C -> false + * - pathToBeFound = X -> false */ public static boolean containsSuperpathOrEquivalent(Collection paths, ItemPath pathToBeFound) { for (ItemPath path : paths) { diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismContainerValueImpl.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismContainerValueImpl.java index 4a809988491..4c1fd05f56b 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismContainerValueImpl.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismContainerValueImpl.java @@ -1672,7 +1672,9 @@ public void keepPaths(List keep) throws SchemaException { for (QName itemName : itemNames) { Item item = findItemByQName(itemName); ItemPath itemPath = item.getPath().removeIds(); - if (!ItemPathCollectionsUtil.containsSuperpathOrEquivalent(keep, itemPath)) { + if (!ItemPathCollectionsUtil.containsSuperpathOrEquivalent(keep, itemPath) + && !ItemPathCollectionsUtil.containsSubpathOrEquivalent(keep, itemPath)) { + System.out.println("Removing " + itemPath + " because not in " + keep); removeItem(ItemName.fromQName(itemName), Item.class); } else { if (item instanceof PrismContainer) { diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/delta/ObjectDeltaImpl.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/delta/ObjectDeltaImpl.java index d477d01f090..d66dcefcd6b 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/delta/ObjectDeltaImpl.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/delta/ObjectDeltaImpl.java @@ -15,6 +15,7 @@ import com.evolveum.midpoint.prism.path.ItemPathCollectionsUtil; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.MiscUtil; +import com.evolveum.midpoint.util.annotation.Experimental; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; @@ -1405,4 +1406,47 @@ public boolean isRedundant(PrismObject object, boolean assumeMissingItems) th throw new AssertionError("Unknown change type: " + changeType); } } + + @Experimental // todo review and write some tests + @Override + public void removeOperationalItems() { + switch (changeType) { + case ADD: + objectToAdd.getValue().removeOperationalItems(); + return; + case MODIFY: + Iterator> iterator = modifications.iterator(); + while (iterator.hasNext()) { + ItemDelta itemDelta = iterator.next(); + ItemDefinition definition = itemDelta.getDefinition(); + if (definition != null && definition.isOperational()) { + iterator.remove(); + } else { + emptyIfNull(itemDelta.getValuesToAdd()).forEach(this::removeOperationalItems); + emptyIfNull(itemDelta.getValuesToDelete()).forEach(this::removeOperationalItems); + emptyIfNull(itemDelta.getValuesToReplace()).forEach(this::removeOperationalItems); + emptyIfNull(itemDelta.getEstimatedOldValues()).forEach(this::removeOperationalItems); + } + } + return; + case DELETE: + // nothing to do here + } + } + + private void removeOperationalItems(PrismValue value) { + if (value instanceof PrismContainerValue) { + ((PrismContainerValue) value).removeOperationalItems(); + } + } + + @Experimental // todo review and write some tests + @Override + public void removeEstimatedOldValues() { + if (changeType == ChangeType.MODIFY) { + for (ItemDelta modification : modifications) { + modification.setEstimatedOldValues(null); + } + } + } } diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/PrismMarshaller.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/PrismMarshaller.java index 56838d46706..d0f11414735 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/PrismMarshaller.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/marshaller/PrismMarshaller.java @@ -212,7 +212,7 @@ private XNodeImpl marshalItemValue(@NotNull PrismValue itemValue, @Nullable Item xnode = serializeReferenceValue((PrismReferenceValue)itemValue, (PrismReferenceDefinition) definition, ctx); } else if (itemValue instanceof PrismPropertyValue) { warnIfItemsToSkip(itemValue, itemsToSkip); - xnode = serializePropertyValue((PrismPropertyValue)itemValue, (PrismPropertyDefinition) definition, typeName); + xnode = serializePropertyValue((PrismPropertyValue)itemValue, (PrismPropertyDefinition) definition, typeName, ctx); } else if (itemValue instanceof PrismContainerValue) { xnode = marshalContainerValue((PrismContainerValue)itemValue, (PrismContainerDefinition) definition, ctx, itemsToSkip); } else { @@ -453,7 +453,8 @@ private QName createReferenceQName(QName qname, String namespace) { //region Serializing properties - specific functionality @NotNull - private XNodeImpl serializePropertyValue(@NotNull PrismPropertyValue value, PrismPropertyDefinition definition, QName typeNameIfNoDefinition) throws SchemaException { + private XNodeImpl serializePropertyValue(@NotNull PrismPropertyValue value, PrismPropertyDefinition definition, + QName typeNameIfNoDefinition, SerializationContext ctx) throws SchemaException { @Nullable QName typeName = definition != null ? definition.getTypeName() : typeNameIfNoDefinition; ExpressionWrapper expression = value.getExpression(); if (expression != null) { @@ -467,7 +468,7 @@ private XNodeImpl serializePropertyValue(@NotNull PrismPropertyValue valu } else if (realValue instanceof PolyStringType) { // should not occur ... return beanMarshaller.marshalPolyString(((PolyStringType) realValue).toPolyString()); } else if (beanMarshaller.canProcess(typeName)) { - XNodeImpl xnode = beanMarshaller.marshall(realValue); + XNodeImpl xnode = beanMarshaller.marshall(realValue, ctx); if (xnode == null) { // Marshaling attempt may process the expression in raw element expression = value.getExpression(); diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd index 102d8c40392..aa7f756b13d 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd @@ -944,6 +944,14 @@ + + + + 4.1 + true + + + @@ -964,6 +972,21 @@ + + + + 4.1 + true + + + + + + + 4.1 + + + @@ -1403,6 +1426,8 @@ + + @@ -2571,6 +2596,7 @@ Kind of operation (captured by OperationResult). + HIGHLY EXPERIMENTAL. Maybe it will be scrapped in the near future. 4.1 @@ -2648,6 +2674,88 @@ + + + + Check whether focus should be loaded. + TODO really? + + + + + + + + + + Focus load check that resulted in focus being loaded. + TODO really? + + + + + + + + + + Projection load. + + + + + + + + + + Full projection load. + + + + + + + + + + Execution of focus change. + + + + + + + + + + Execution of projection change. + + + + + + + + + + Provisioning API operation. + + + + + + + + + + Connector operation. + + + + + + @@ -2674,6 +2782,13 @@ + + + + Specification of the data flow(s) to be visualized. + + + @@ -2753,10 +2868,10 @@ - + - Should operational items be displayed? + What kind of data are to be shown? The interpretation is left to the particular trace visualizer. @@ -2807,6 +2922,16 @@ + + + + The trace is visualized in brief form - potentially multiline but less detailed. + + + + + + @@ -2817,6 +2942,97 @@ + + + + The trace is visualized in full details. + + + + + + + + + + + + + Generic data selection rule. + + + 4.1 + true + + + + + + + No "instance" data shown. + + + + + + + + + + Non-operational data shown. + + + + + + + + + + All data is shown. + + + + + + + + + + + Specification of the data flow(s) to be visualized. + + + true + true + 4.1 + + + + + + + Initial set of items to be shown. + + + + + + + Whether to look backward i.e. to the source(s) for specified item(s). + + + + + + + Whether to look forward i.e. to the target(s) derived from specified item(s). + + + + + + diff --git a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingImpl.java b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingImpl.java index 6430401d940..5dfab902ec0 100644 --- a/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingImpl.java +++ b/model/model-common/src/main/java/com/evolveum/midpoint/model/common/mapping/MappingImpl.java @@ -64,6 +64,8 @@ public class MappingImpl // configuration properties (unmodifiable) private final MappingType mappingType; private final MappingKindType mappingKind; + private final ItemPath implicitSourcePath; + private final ItemPath implicitTargetPath; private final ExpressionFactory expressionFactory; private final ExpressionVariables variables; private final PrismContext prismContext; @@ -138,6 +140,8 @@ private MappingImpl(Builder builder) { variables = builder.variables; mappingType = builder.mappingType; mappingKind = builder.mappingKind; + implicitSourcePath = builder.implicitSourcePath; + implicitTargetPath = builder.implicitTargetPath; objectResolver = builder.objectResolver; securityContextManager = builder.securityContextManager; defaultSource = builder.defaultSource; @@ -407,6 +411,8 @@ public void evaluate(Task task, OperationResult parentResult) throws ExpressionE trace = new MappingEvaluationTraceType(prismContext) .mapping(mappingType.clone()) .mappingKind(mappingKind) + .implicitSourcePath(implicitSourcePath != null ? new ItemPathType(implicitSourcePath) : null) + .implicitTargetPath(implicitTargetPath != null ? new ItemPathType(implicitTargetPath) : null) .containingObjectRef(ObjectTypeUtil.createObjectRef(originObject, prismContext)); trace.setMapping(mappingType.clone()); result.addTrace(trace); @@ -1278,6 +1284,8 @@ public PrismValueDeltaSetTripleProducer clone() { MappingImpl clone = new Builder() .mappingType(mappingType) .mappingKind(mappingKind) + .implicitSourcePath(implicitSourcePath) + .implicitTargetPath(implicitTargetPath) .contextDescription(contextDescription) .expressionFactory(expressionFactory) .securityContextManager(securityContextManager) @@ -1466,6 +1474,8 @@ public static final class Builder defaultSource; @@ -1511,6 +1521,16 @@ public Builder mappingKind(MappingKindType val) { return this; } + public Builder implicitSourcePath(ItemPath val) { + implicitSourcePath = val; + return this; + } + + public Builder implicitTargetPath(ItemPath val) { + implicitTargetPath = val; + return this; + } + public Builder objectResolver(ObjectResolver val) { objectResolver = val; return this; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Construction.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Construction.java index 048a7f55412..227f0a4c65e 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Construction.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Construction.java @@ -13,6 +13,7 @@ import javax.xml.namespace.QName; +import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; import com.evolveum.midpoint.common.refinery.RefinedAssociationDefinition; @@ -467,7 +468,8 @@ private MappingImpl, ResourceAttributeDefinition> e //noinspection CaughtExceptionImmediatelyRethrown try { - evaluatedMapping = evaluateMapping(builder, attrName, outputDefinition, null, task, result); + evaluatedMapping = evaluateMapping(builder, ShadowType.F_ATTRIBUTES.append(attrName), + attrName, outputDefinition, null, task, result); } catch (SchemaException e) { throw new SchemaException(getAttributeEvaluationErrorMessage(attrName, e), e); @@ -582,16 +584,17 @@ private MappingImpl, PrismContainerDe .originType(OriginType.ASSIGNMENTS) .originObject(getSource()); + ItemPath implicitTargetPath = ShadowType.F_ASSOCIATION.append(assocName); // not quite correct MappingImpl, PrismContainerDefinition> evaluatedMapping = evaluateMapping( - mappingBuilder, assocName, outputDefinition, rAssocDef.getAssociationTarget(), task, result); + mappingBuilder, implicitTargetPath, assocName, outputDefinition, rAssocDef.getAssociationTarget(), task, result); - LOGGER.trace("Evaluated mapping for association " + assocName + ": " + evaluatedMapping); + LOGGER.trace("Evaluated mapping for association {}: {}", assocName, evaluatedMapping); return evaluatedMapping; } @SuppressWarnings("ConstantConditions") private > MappingImpl evaluateMapping( - MappingImpl.Builder builder, QName mappingQName, D outputDefinition, + MappingImpl.Builder builder, ItemPath implicitTargetPath, QName mappingQName, D outputDefinition, RefinedObjectClassDefinition assocTargetObjectClassDefinition, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, CommunicationException { @@ -601,6 +604,7 @@ private > MappingImpl ev builder = builder.mappingQName(mappingQName) .mappingKind(MappingKindType.CONSTRUCTION) + .implicitTargetPath(implicitTargetPath) .sourceContext(getFocusOdo()) .defaultTargetDefinition(outputDefinition) .originType(getOriginType()) diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java index 1756f97919d..06af7be080a 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ActivationProcessor.java @@ -7,6 +7,7 @@ package com.evolveum.midpoint.model.impl.lens.projector; import com.evolveum.midpoint.model.impl.lens.projector.mappings.*; +import com.evolveum.midpoint.prism.path.ItemName; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; import com.evolveum.midpoint.repo.common.expression.Source; @@ -71,10 +72,10 @@ public class ActivationProcessor { private static final Trace LOGGER = TraceManager.getTrace(ActivationProcessor.class); - private static final QName SHADOW_EXISTS_PROPERTY_NAME = new QName(SchemaConstants.NS_C, "shadowExists"); - private static final QName LEGAL_PROPERTY_NAME = new QName(SchemaConstants.NS_C, "legal"); - private static final QName ASSIGNED_PROPERTY_NAME = new QName(SchemaConstants.NS_C, "assigned"); - private static final QName FOCUS_EXISTS_PROPERTY_NAME = new QName(SchemaConstants.NS_C, "focusExists"); + private static final ItemName SHADOW_EXISTS_PROPERTY_NAME = new ItemName(SchemaConstants.NS_C, "shadowExists"); + private static final ItemName LEGAL_PROPERTY_NAME = new ItemName(SchemaConstants.NS_C, "legal"); + private static final ItemName ASSIGNED_PROPERTY_NAME = new ItemName(SchemaConstants.NS_C, "assigned"); + private static final ItemName FOCUS_EXISTS_PROPERTY_NAME = new ItemName(SchemaConstants.NS_C, "focusExists"); @Autowired private ContextLoader contextLoader; @Autowired private PrismContext prismContext; @@ -482,7 +483,9 @@ private boolean evaluateExistenceMapping(final LensContext params.setContext(context); params.setInitializer(builder -> { - builder.mappingKind(MappingKindType.OUTBOUND); + builder.mappingKind(MappingKindType.OUTBOUND) + .implicitSourcePath(LEGAL_PROPERTY_NAME) + .implicitTargetPath(SHADOW_EXISTS_PROPERTY_NAME); // Source: legal ItemDeltaItem,PrismPropertyDefinition> legalSourceIdi = getLegalIdi(projCtx); @@ -575,6 +578,7 @@ private void evaluateActivationMapping(final LensContex MappingInitializer,PrismPropertyDefinition> initializer = builder -> { builder.mappingKind(MappingKindType.OUTBOUND); + builder.implicitTargetPath(projectionPropertyPath); // Source: administrativeStatus, validFrom or validTo ItemDeltaItem,PrismPropertyDefinition> sourceIdi = context.getFocusContext().getObjectDeltaObject().findIdi(focusPropertyPath); @@ -584,19 +588,19 @@ private void evaluateActivationMapping(final LensContex ActivationValidityCapabilityType capValidTo = CapabilityUtil.getEffectiveActivationValidTo(capActivation); // Source: computedShadowStatus - ItemDeltaItem,PrismPropertyDefinition> computedIdi; + ItemPath sourcePath; if (capValidFrom != null && capValidTo != null) { // "Native" validFrom and validTo, directly use administrativeStatus - computedIdi = context.getFocusContext().getObjectDeltaObject().findIdi(focusPropertyPath); - + sourcePath = focusPropertyPath; } else { // Simulate validFrom and validTo using effectiveStatus - computedIdi = context.getFocusContext().getObjectDeltaObject().findIdi(SchemaConstants.PATH_ACTIVATION_EFFECTIVE_STATUS); - + sourcePath = SchemaConstants.PATH_ACTIVATION_EFFECTIVE_STATUS; } + ItemDeltaItem, PrismPropertyDefinition> computedIdi = + context.getFocusContext().getObjectDeltaObject().findIdi(sourcePath); + builder.implicitSourcePath(sourcePath); Source,PrismPropertyDefinition> computedSource = new Source<>(computedIdi, ExpressionConstants.VAR_INPUT_QNAME); - builder.defaultSource(computedSource); Source,PrismPropertyDefinition> source = new Source<>(sourceIdi, ExpressionConstants.VAR_ADMINISTRATIVE_STATUS_QNAME); @@ -605,6 +609,7 @@ private void evaluateActivationMapping(final LensContex } else { Source,PrismPropertyDefinition> source = new Source<>(sourceIdi, ExpressionConstants.VAR_INPUT_QNAME); builder.defaultSource(source); + builder.implicitSourcePath(focusPropertyPath); } // Source: legal @@ -658,6 +663,7 @@ private void evaluateOutboundMapping(final LensContext< builder.originType(OriginType.OUTBOUND); builder.mappingKind(MappingKindType.OUTBOUND); builder.originObject(projCtx.getResource()); + builder.implicitTargetPath(projectionPropertyPath); initializer.initialize(builder); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ContextLoader.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ContextLoader.java index 5a094acdc26..74b04ed4b4e 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ContextLoader.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ContextLoader.java @@ -1,1656 +1,1673 @@ -/* - * Copyright (c) 2010-2018 Evolveum and contributors - * - * This work is dual-licensed under the Apache License 2.0 - * and European Union Public License. See LICENSE file for details. - */ -package com.evolveum.midpoint.model.impl.lens.projector; - -import static com.evolveum.midpoint.model.impl.lens.LensUtil.getExportType; -import static com.evolveum.midpoint.schema.internals.InternalsConfig.consistencyChecks; -import static com.evolveum.midpoint.schema.result.OperationResult.DEFAULT; - -import java.util.Collection; -import java.util.Iterator; -import java.util.List; - -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.*; -import com.evolveum.midpoint.schema.*; -import com.evolveum.midpoint.schema.constants.ObjectTypes; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.internals.InternalsConfig; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; - -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.Validate; -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.refinery.RefinedObjectClassDefinition; -import com.evolveum.midpoint.model.api.ModelExecuteOptions; -import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; -import com.evolveum.midpoint.model.common.ArchetypeManager; -import com.evolveum.midpoint.model.common.SystemObjectCache; -import com.evolveum.midpoint.model.impl.lens.ClockworkMedic; -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensElementContext; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; -import com.evolveum.midpoint.model.impl.lens.LensObjectDeltaOperation; -import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; -import com.evolveum.midpoint.model.impl.lens.LensUtil; -import com.evolveum.midpoint.model.api.context.SynchronizationIntent; -import com.evolveum.midpoint.model.impl.security.SecurityHelper; -import com.evolveum.midpoint.provisioning.api.ProvisioningService; -import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.ExceptionUtil; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -import com.evolveum.midpoint.schema.util.ShadowUtil; -import com.evolveum.midpoint.task.api.Task; -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.ObjectAlreadyExistsException; -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.exception.SystemException; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; - -/** - * Context loader loads the missing parts of the context. The context enters the projector with just the minimum information. - * Context loader gets missing data such as accounts. It gets them from the repository or provisioning as necessary. It follows - * the account links in focus (linkRef) and focus deltas. - * - * @author Radovan Semancik - * - */ -@Component -public class ContextLoader { - - @Autowired - @Qualifier("cacheRepositoryService") - private transient RepositoryService cacheRepositoryService; - - @Autowired private SystemObjectCache systemObjectCache; - @Autowired private ArchetypeManager archetypeManager; - @Autowired private ProvisioningService provisioningService; - @Autowired private PrismContext prismContext; - @Autowired private SecurityHelper securityHelper; - @Autowired private ClockworkMedic medic; - - private static final Trace LOGGER = TraceManager.getTrace(ContextLoader.class); - - public static final String CLASS_DOT = ContextLoader.class.getName() + "."; - private static final String OPERATION_LOAD = CLASS_DOT + "load"; - private static final String OPERATION_LOAD_PROJECTION = CLASS_DOT + "loadProjection"; - - public void load(LensContext context, String activityDescription, - Task task, OperationResult parentResult) - throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, - SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { - - context.checkAbortRequested(); - - context.recompute(); - - OperationResult result = parentResult.createMinorSubresult(OPERATION_LOAD); - ProjectorComponentTraceType trace; - if (result.isTraced()) { - trace = new ProjectorComponentTraceType(prismContext); - if (result.isTracingNormal(ProjectorComponentTraceType.class)) { - trace.setInputLensContextText(context.debugDump()); - } - trace.setInputLensContext(context.toLensContextType(getExportType(trace, result))); - result.addTrace(trace); - } else { - trace = null; - } - - try { - - for (LensProjectionContext projectionContext: context.getProjectionContexts()) { - preprocessProjectionContext(context, projectionContext, task, result); - } - - if (consistencyChecks) context.checkConsistence(); - - determineFocusContext(context, task, result); - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext != null) { - - context.recomputeFocus(); - - loadFromSystemConfig(context, task, result); - - if (FocusType.class.isAssignableFrom(context.getFocusClass())) { - // this also removes the accountRef deltas - //noinspection unchecked - loadLinkRefs((LensContext)context, task, result); - LOGGER.trace("loadLinkRefs done"); - } - - // Some cleanup - if (focusContext.getPrimaryDelta() != null && focusContext.getPrimaryDelta().isModify() && focusContext.getPrimaryDelta().isEmpty()) { - focusContext.setPrimaryDelta(null); - } - - for (LensProjectionContext projectionContext: context.getProjectionContexts()) { - if (projectionContext.getSynchronizationIntent() != null) { - // Accounts with explicitly set intent are never rotten. These are explicitly requested actions - // if they fail then they really should fail. - projectionContext.setFresh(true); - } - } - - setPrimaryDeltaOldValue(focusContext); - - } else { - // Projection contexts are not rotten in this case. There is no focus so there is no way to refresh them. - for (LensProjectionContext projectionContext: context.getProjectionContexts()) { - projectionContext.setFresh(true); - } - } - - removeRottenContexts(context); - - if (consistencyChecks) context.checkConsistence(); - - for (LensProjectionContext projectionContext: context.getProjectionContexts()) { - context.checkAbortRequested(); - // TODO: not perfect. Practically, we want loadProjection operation to contain all the projection - // results. But for that we would need code restructure. - OperationResult projectionResult = result.createMinorSubresult(OPERATION_LOAD_PROJECTION); - try { - finishLoadOfProjectionContext(context, projectionContext, task, projectionResult); - } catch (Throwable e) { - projectionResult.recordFatalError(e); - throw e; - } - projectionResult.computeStatus(); - } - - if (consistencyChecks) context.checkConsistence(); - - context.recompute(); - - if (consistencyChecks) { - fullCheckConsistence(context); - } - - medic.traceContext(LOGGER, activityDescription, "after load", false, context, false); - - result.computeStatusComposite(); - - } catch (Throwable e) { - result.recordFatalError(e); - throw e; - } finally { - if (trace != null) { - if (result.isTracingNormal(ProjectorComponentTraceType.class)) { - trace.setOutputLensContextText(context.debugDump()); - } - trace.setOutputLensContext(context.toLensContextType(getExportType(trace, result))); - } - } - } - - - /** - * Removes projection contexts that are not fresh. - * These are usually artifacts left after the context reload. E.g. an account that used to be linked to a user before - * but was removed in the meantime. - */ - private void removeRottenContexts(LensContext context) { - Iterator projectionIterator = context.getProjectionContextsIterator(); - while (projectionIterator.hasNext()) { - LensProjectionContext projectionContext = projectionIterator.next(); - if (projectionContext.getPrimaryDelta() != null && !projectionContext.getPrimaryDelta().isEmpty()) { - // We must never remove contexts with primary delta. Even though it fails later on. - // What the user wishes should be done (or at least attempted) regardless of the consequences. - // Vox populi vox dei - continue; - } - if (projectionContext.getWave() >= context.getExecutionWave()) { - // We must not remove context from this and later execution waves. These haven't had the - // chance to be executed yet - continue; - } - ResourceShadowDiscriminator discr = projectionContext.getResourceShadowDiscriminator(); - if (discr != null && discr.getOrder() > 0) { - // HACK never rot higher-order context. TODO: check if lower-order context is rotten, the also rot this one - continue; - } - if (!projectionContext.isFresh()) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Removing rotten context {}", projectionContext.getHumanReadableName()); - } - - if (projectionContext.isToBeArchived()) { - context.getHistoricResourceObjects().add(projectionContext.getResourceShadowDiscriminator()); - } - - List> executedDeltas = projectionContext.getExecutedDeltas(); - context.getRottenExecutedDeltas().addAll(executedDeltas); - projectionIterator.remove(); - } - } - } - - - /** - * Make sure that the projection context is loaded as appropriate. - */ - public void makeSureProjectionIsLoaded(LensContext context, - LensProjectionContext projectionContext, Task task, OperationResult result) throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - preprocessProjectionContext(context, projectionContext, task, result); - finishLoadOfProjectionContext(context, projectionContext, task, result); - } - - /** - * Make sure that the context is OK and consistent. It means that is has a resource, it has correctly processed - * discriminator, etc. - */ - private void preprocessProjectionContext(LensContext context, - LensProjectionContext projectionContext, Task task, OperationResult result) - throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - if (!ShadowType.class.isAssignableFrom(projectionContext.getObjectTypeClass())) { - return; - } - String resourceOid = null; - boolean isThombstone = false; - ShadowKindType kind = ShadowKindType.ACCOUNT; - String intent = null; - String tag = null; - int order = 0; - ResourceShadowDiscriminator rsd = projectionContext.getResourceShadowDiscriminator(); - if (rsd != null) { - resourceOid = rsd.getResourceOid(); - isThombstone = rsd.isTombstone(); - kind = rsd.getKind(); - intent = rsd.getIntent(); - tag = rsd.getTag(); - order = rsd.getOrder(); - } - if (resourceOid == null && projectionContext.getObjectCurrent() != null) { - resourceOid = ShadowUtil.getResourceOid(projectionContext.getObjectCurrent().asObjectable()); - } - if (resourceOid == null && projectionContext.getObjectNew() != null) { - resourceOid = ShadowUtil.getResourceOid(projectionContext.getObjectNew().asObjectable()); - } - // We still may not have resource OID here. E.g. in case of the delete when the account is not loaded yet. It is - // perhaps safe to skip this. It will be sorted out later. - - if (resourceOid != null) { - if (intent == null && projectionContext.getObjectNew() != null) { - ShadowType shadowNewType = projectionContext.getObjectNew().asObjectable(); - kind = ShadowUtil.getKind(shadowNewType); - intent = ShadowUtil.getIntent(shadowNewType); - tag = shadowNewType.getTag(); - } - ResourceType resource = projectionContext.getResource(); - if (resource == null) { - resource = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result); - projectionContext.setResource(resource); - } - String refinedIntent = LensUtil.refineProjectionIntent(kind, intent, resource, prismContext); - rsd = new ResourceShadowDiscriminator(resourceOid, kind, refinedIntent, tag, isThombstone); - rsd.setOrder(order); - projectionContext.setResourceShadowDiscriminator(rsd); - } - if (projectionContext.getOid() == null && rsd != null && rsd.getOrder() != 0) { - // Try to determine OID from lower-order contexts - for (LensProjectionContext aProjCtx: context.getProjectionContexts()) { - ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator(); - if (rsd.equivalent(aDiscr) && aProjCtx.getOid() != null) { - projectionContext.setOid(aProjCtx.getOid()); - break; - } - } - } - } - - /** - * try to load focus context from oid, delta, projections (e.g. by determining account owners) - */ - public void determineFocusContext(LensContext context, Task task, OperationResult parentResult) - throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - - OperationResult result = parentResult.subresult(CLASS_DOT + "determineFocusContext") - .setMinor() - .build(); - FocusLoadedTraceType trace; - if (result.isTraced()) { - trace = new FocusLoadedTraceType(prismContext); - if (result.isTracingNormal(FocusLoadedTraceType.class)) { - trace.setInputLensContextText(context.debugDump()); - } - trace.setInputLensContext(context.toLensContextType(getExportType(trace, result))); - result.addTrace(trace); - } else { - trace = null; - } - LensFocusContext focusContext = context.getFocusContext(); - try { - if (focusContext == null) { - focusContext = determineFocusContextFromProjections(context, result); - } - - if (focusContext == null) { - result.addReturnComment("Nothing to load"); - return; - } - - // Make sure that we RELOAD the focus object if the context is not fresh - // the focus may have changed in the meantime - if (focusContext.getObjectCurrent() != null && focusContext.isFresh()) { - result.addReturnComment("Already loaded"); - return; - } - ObjectDelta objectDelta = focusContext.getDelta(); - if (objectDelta != null && objectDelta.isAdd() && focusContext.getExecutedDeltas().isEmpty()) { - //we're adding the focal object. No need to load it, it is in the delta - focusContext.setFresh(true); - result.addReturnComment("Obtained from delta"); - return; - } - if (focusContext.getObjectCurrent() != null && objectDelta != null && objectDelta.isDelete()) { - // do not reload if the delta is delete. the reload will most likely fail anyway - // but DO NOT set the fresh flag in this case, it may be misleading - result.addReturnComment("Not loading as delta is DELETE"); - return; - } - - String focusOid = focusContext.getOid(); - if (StringUtils.isBlank(focusOid)) { - throw new IllegalArgumentException("No OID in primary focus delta"); - } - - PrismObject object; - if (ObjectTypes.isClassManagedByProvisioning(focusContext.getObjectTypeClass())) { - object = provisioningService.getObject(focusContext.getObjectTypeClass(), focusOid, - SelectorOptions.createCollection(GetOperationOptions.createNoFetch()), task, result); - result.addReturnComment("Loaded via provisioning"); - } else { - - // Always load a complete object here, including the not-returned-by-default properties. - // This is temporary measure to make sure that the mappings will have all they need. - // See MID-2635 - Collection> options = - SelectorOptions.createCollection(GetOperationOptions.createRetrieve(RetrieveOption.INCLUDE)); - object = cacheRepositoryService.getObject(focusContext.getObjectTypeClass(), focusOid, options, result); - result.addReturnComment("Loaded from repository"); - } - - focusContext.setLoadedObject(object); - focusContext.setFresh(true); - LOGGER.trace("Focal object loaded: {}", object); - } catch (Throwable t) { - result.recordFatalError(t); - throw t; - } finally { - if (trace != null) { - if (result.isTracingNormal(FocusLoadedTraceType.class)) { - trace.setOutputLensContextText(context.debugDump()); - } - trace.setOutputLensContext(context.toLensContextType(getExportType(trace, result))); - } - result.computeStatusIfUnknown(); - } - } - - private LensFocusContext determineFocusContextFromProjections(LensContext context, OperationResult result) { - String focusOid = null; - LensProjectionContext projectionContextThatYeildedFocusOid = null; - PrismObject focusOwner = null; - for (LensProjectionContext projectionContext: context.getProjectionContexts()) { - String projectionOid = projectionContext.getOid(); - if (projectionOid != null) { - PrismObject shadowOwner = cacheRepositoryService.searchShadowOwner(projectionOid, - SelectorOptions.createCollection(GetOperationOptions.createAllowNotFound()), - result); - if (shadowOwner != null) { - if (focusOid == null || focusOid.equals(shadowOwner.getOid())) { - focusOid = shadowOwner.getOid(); - //noinspection unchecked - focusOwner = (PrismObject) shadowOwner; - projectionContextThatYeildedFocusOid = projectionContext; - } else { - throw new IllegalArgumentException("The context does not have explicit focus. Attempt to determine focus failed because two " + - "projections points to different foci: "+projectionContextThatYeildedFocusOid+"->"+focusOid+"; "+ - projectionContext+"->"+shadowOwner); - } - } - } - } - - if (focusOid != null) { - LensFocusContext focusCtx = context.getOrCreateFocusContext(focusOwner.getCompileTimeClass()); - focusCtx.setOid(focusOid); - return focusCtx; - } - - return null; - } - - private void setPrimaryDeltaOldValue(LensElementContext ctx) { - if (ctx.getPrimaryDelta() != null && ctx.getObjectOld() != null && ctx.isModify()) { - boolean freezeAfterChange; - if (ctx.getPrimaryDelta().isImmutable()) { - ctx.setPrimaryDelta(ctx.getPrimaryDelta().clone()); - freezeAfterChange = true; - } else { - freezeAfterChange = false; - } - for (ItemDelta itemDelta: ctx.getPrimaryDelta().getModifications()) { - LensUtil.setDeltaOldValue(ctx, itemDelta); - } - if (freezeAfterChange) { - ctx.getPrimaryDelta().freeze(); - } - } - } - - private void loadFromSystemConfig(LensContext context, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException, CommunicationException, SecurityViolationException { - PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(result); - if (systemConfiguration == null) { - // This happens in some tests. And also during first startup. - return; - } - context.setSystemConfiguration(systemConfiguration); - SystemConfigurationType systemConfigurationType = systemConfiguration.asObjectable(); - - if (context.getFocusContext() != null) { - if (context.getFocusContext().getArchetypePolicyType() == null) { - ArchetypePolicyType archetypePolicy = determineArchetypePolicy(context, task, result); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Selected archetype policy:\n{}", - archetypePolicy==null?null:archetypePolicy.asPrismContainerValue().debugDump(1)); - } - context.getFocusContext().setArchetypePolicyType(archetypePolicy); - } - } - - if (context.getFocusTemplate() == null) { - // TODO is the nullity check needed here? - setFocusTemplate(context, result); - } - - if (context.getAccountSynchronizationSettings() == null) { - ProjectionPolicyType globalAccountSynchronizationSettings = systemConfigurationType.getGlobalAccountSynchronizationSettings(); - LOGGER.trace("Applying globalAccountSynchronizationSettings to context: {}", globalAccountSynchronizationSettings); - context.setAccountSynchronizationSettings(globalAccountSynchronizationSettings); - } - - loadSecurityPolicy(context, task, result); - } - - private ArchetypePolicyType determineArchetypePolicy(LensContext context, Task task, OperationResult result) throws SchemaException, ConfigurationException { - PrismObject systemConfiguration = context.getSystemConfiguration(); - if (systemConfiguration == null) { - return null; - } - if (context.getFocusContext() == null) { - return null; - } - PrismObject object = context.getFocusContext().getObjectAny(); - String explicitArchetypeOid = LensUtil.determineExplicitArchetypeOid(context.getFocusContext().getObjectAny()); - return archetypeManager.determineArchetypePolicy(object, explicitArchetypeOid, result); - } - - public ArchetypeType updateArchetype(LensContext context, Task task, OperationResult result) throws SchemaException, ConfigurationException { - PrismObject systemConfiguration = context.getSystemConfiguration(); - if (systemConfiguration == null) { - return null; - } - if (context.getFocusContext() == null) { - return null; - } - - PrismObject object = context.getFocusContext().getObjectAny(); - - String explicitArchetypeOid = LensUtil.determineExplicitArchetypeOid(context.getFocusContext().getObjectAny()); - PrismObject archetype = archetypeManager.determineArchetype(object, explicitArchetypeOid, result); - ArchetypeType archetypeType = null; - if (archetype != null) { - archetypeType = archetype.asObjectable(); - } - - context.getFocusContext().setArchetype(archetypeType); - - return archetypeType; - } - - public void updateArchetypePolicy(LensContext context, Task task, OperationResult result) throws SchemaException, ConfigurationException { - if (context.getFocusContext() == null) { - return; - } - ArchetypePolicyType newArchetypePolicy = determineArchetypePolicy(context, task, result); - if (newArchetypePolicy != context.getFocusContext().getArchetypePolicyType()) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Changed policy configuration because of changed subtypes:\n{}", - newArchetypePolicy==null?null:newArchetypePolicy.asPrismContainerValue().debugDump(1)); - } - context.getFocusContext().setArchetypePolicyType(newArchetypePolicy); - } - } - - // expects that object policy configuration is already set in focusContext - public void setFocusTemplate(LensContext context, OperationResult result) - throws ObjectNotFoundException, SchemaException { - - // 1. When this method is called after inbound processing, we might want to change the existing template - // (because e.g. subtype or archetype was determined and we want to move from generic to more specific template). - // 2. On the other hand, if focus template is set up explicitly from the outside (e.g. in synchronization section) - // we probably do not want to change it here. - if (context.getFocusTemplate() != null && context.isFocusTemplateExternallySet()) { - return; - } - - String currentOid = context.getFocusTemplate() != null ? context.getFocusTemplate().getOid() : null; - String newOid; - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - newOid = null; - } else { - ArchetypePolicyType archetypePolicy = focusContext.getArchetypePolicyType(); - if (archetypePolicy == null) { - LOGGER.trace("No default object template (no policy)"); - newOid = null; - } else { - ObjectReferenceType templateRef = archetypePolicy.getObjectTemplateRef(); - if (templateRef == null) { - LOGGER.trace("No default object template (no templateRef)"); - newOid = null; - } else { - newOid = templateRef.getOid(); - } - } - } - - LOGGER.trace("current focus template OID = {}, new = {}", currentOid, newOid); - if (!java.util.Objects.equals(currentOid, newOid)) { - ObjectTemplateType template; - if (newOid != null) { - template = cacheRepositoryService.getObject(ObjectTemplateType.class, newOid, null, result).asObjectable(); - } else { - template = null; - } - context.setFocusTemplate(template); - } - } - - private void loadLinkRefs(LensContext context, Task task, OperationResult result) throws ObjectNotFoundException, - SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - // Nothing to load - return; - } - - LOGGER.trace("loadLinkRefs starting"); - - PrismObject focusCurrent = focusContext.getObjectCurrent(); - if (focusCurrent != null) { - loadLinkRefsFromFocus(context, focusCurrent, task, result); - LOGGER.trace("loadLinkRefsFromFocus done"); - } - - if (consistencyChecks) context.checkConsistence(); - - loadLinkRefsFromDelta(context, focusCurrent, focusContext, task, result); - LOGGER.trace("loadLinkRefsFromDelta done"); - - if (consistencyChecks) context.checkConsistence(); - - loadProjectionContextsSync(context, task, result); - LOGGER.trace("loadProjectionContextsSync done"); - - if (consistencyChecks) context.checkConsistence(); - } - - /** - * Does not overwrite existing account contexts, just adds new ones. - */ - private void loadLinkRefsFromFocus(LensContext context, PrismObject focus, - Task task, OperationResult result) throws ObjectNotFoundException, - CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { - PrismReference linkRef = focus.findReference(FocusType.F_LINK_REF); - if (linkRef == null) { - return; - } - for (PrismReferenceValue linkRefVal : linkRef.getValues()) { - String oid = linkRefVal.getOid(); - if (StringUtils.isBlank(oid)) { - LOGGER.trace("Null or empty OID in link reference {} in:\n{}", linkRef, - focus.debugDump(1)); - throw new SchemaException("Null or empty OID in link reference in " + focus); - } - LensProjectionContext existingAccountContext = findAccountContext(oid, context); - -// if (!canBeLoaded(context, existingAccountContext)) { -// continue; -// } - - if (existingAccountContext != null) { - // TODO: do we need to reload the account inside here? yes we need - - existingAccountContext.setFresh(true); - continue; - } - PrismObject shadow; - //noinspection unchecked - PrismObject shadowFromLink = linkRefVal.getObject(); - if (shadowFromLink == null) { - GetOperationOptions rootOpts; - if (context.isDoReconciliationForAllProjections()) { - rootOpts = GetOperationOptions.createForceRetry(); - } else { - // Using NO_FETCH so we avoid reading in a full account. This is more efficient as we don't need full account here. - // We need to fetch from provisioning and not repository so the correct definition will be set. - rootOpts = GetOperationOptions.createNoFetch(); - rootOpts.setPointInTimeType(PointInTimeType.FUTURE); - } - - Collection> options = SelectorOptions.createCollection(rootOpts); - LOGGER.trace("Loading shadow {} from linkRef, options={}", oid, options); - try { - shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); - } catch (ObjectNotFoundException e) { - // Broken accountRef. We need to mark it for deletion - LensProjectionContext projectionContext = getOrCreateEmptyThombstoneProjectionContext(context, oid); - projectionContext.setFresh(true); - projectionContext.setExists(false); - projectionContext.setShadowExistsInRepo(false); - OperationResult getObjectSubresult = result.getLastSubresult(); - getObjectSubresult.setErrorsHandled(); - continue; - } - } else { - shadow = shadowFromLink; - // Make sure it has a proper definition. This may come from outside of the model. - provisioningService.applyDefinition(shadow, task, result); - } - LensProjectionContext projectionContext = getOrCreateAccountContext(context, shadow, task, result); - projectionContext.setFresh(true); - projectionContext.setExists(ShadowUtil.isExists(shadow.asObjectable())); - if (ShadowUtil.isDead(shadow.asObjectable())) { - projectionContext.markTombstone(); - LOGGER.trace("Loading dead shadow {} for projection {}.", shadow, projectionContext.getHumanReadableName()); - continue; - } - if (context.isDoReconciliationForAllProjections()) { - projectionContext.setDoReconciliation(true); - } - if (projectionContext.isDoReconciliation()) { - // Do not load old account now. It will get loaded later in the - // reconciliation step. - continue; - } - projectionContext.setLoadedObject(shadow); - } - } - - private void loadLinkRefsFromDelta(LensContext context, PrismObject focus, - LensFocusContext focusContext, Task task, OperationResult result) throws SchemaException, - ObjectNotFoundException, CommunicationException, ConfigurationException, - SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { - - ObjectDelta focusPrimaryDelta = focusContext.getPrimaryDelta(); - if (focusPrimaryDelta == null) { - return; - } - - ReferenceDelta linkRefDelta; - if (focusPrimaryDelta.getChangeType() == ChangeType.ADD) { - PrismReference linkRef = focusPrimaryDelta.getObjectToAdd().findReference( - FocusType.F_LINK_REF); - if (linkRef == null) { - // Adding new focus with no linkRef -> nothing to do - return; - } - linkRefDelta = linkRef.createDelta(FocusType.F_LINK_REF); - linkRefDelta.addValuesToAdd(PrismValueCollectionsUtil.cloneValues(linkRef.getValues())); - } else if (focusPrimaryDelta.getChangeType() == ChangeType.MODIFY) { - linkRefDelta = focusPrimaryDelta.findReferenceModification(FocusType.F_LINK_REF); - if (linkRefDelta == null) { - return; - } - } else { - // delete, all existing account are already marked for delete - return; - } - - if (linkRefDelta.isReplace()) { - // process "replace" by distributing values to delete and add - linkRefDelta = (ReferenceDelta) linkRefDelta.clone(); - PrismReference linkRef = focus.findReference(FocusType.F_LINK_REF); - linkRefDelta.distributeReplace(linkRef == null ? null : linkRef.getValues()); - } - - if (linkRefDelta.getValuesToAdd() != null) { - for (PrismReferenceValue refVal : linkRefDelta.getValuesToAdd()) { - String oid = refVal.getOid(); - LensProjectionContext projectionContext = null; - PrismObject shadow = null; - boolean isCombinedAdd = false; - if (oid == null) { - // Adding new account - shadow = refVal.getObject(); - if (shadow == null) { - throw new SchemaException("Null or empty OID in account reference " + refVal + " in " - + focus); - } - provisioningService.applyDefinition(shadow, task, result); - if (consistencyChecks) ShadowUtil.checkConsistence(shadow, "account from "+linkRefDelta); - // Check for conflicting change - projectionContext = LensUtil.getProjectionContext(context, shadow, provisioningService, prismContext, task, result); - if (projectionContext != null) { - // There is already existing context for the same discriminator. Tolerate this only if - // the deltas match. It is an error otherwise. - ObjectDelta primaryDelta = projectionContext.getPrimaryDelta(); - if (primaryDelta == null) { - throw new SchemaException("Attempt to add "+shadow+" to a focus that already contains "+ - projectionContext.getHumanReadableKind()+" of type '"+ - projectionContext.getResourceShadowDiscriminator().getIntent()+"' on "+projectionContext.getResource()); - } - if (!primaryDelta.isAdd()) { - throw new SchemaException("Conflicting changes in the context. " + - "Add of linkRef in the focus delta with embedded object conflicts with explicit delta "+primaryDelta); - } - if (!shadow.equals(primaryDelta.getObjectToAdd())) { - throw new SchemaException("Conflicting changes in the context. " + - "Add of linkRef in the focus delta with embedded object is not adding the same object as explicit delta "+primaryDelta); - } - } else { - // Create account context from embedded object - projectionContext = createProjectionContext(context, shadow, task, result); - } - // This is a new account that is to be added. So it should - // go to account primary delta - ObjectDelta accountPrimaryDelta = shadow.createAddDelta(); - projectionContext.setPrimaryDelta(accountPrimaryDelta); - projectionContext.setFullShadow(true); - projectionContext.setExists(false); - isCombinedAdd = true; - } else { - // We have OID. This is either linking of existing account or - // add of new account - // therefore check for account existence to decide - try { - // Using NO_FETCH so we avoid reading in a full account. This is more efficient as we don't need full account here. - // We need to fetch from provisioning and not repository so the correct definition will be set. - GetOperationOptions rootOpts = GetOperationOptions.createNoFetch(); - rootOpts.setPointInTimeType(PointInTimeType.FUTURE); - Collection> options = SelectorOptions.createCollection(rootOpts); - shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); - // Create account context from retrieved object - projectionContext = getOrCreateAccountContext(context, shadow, task, result); - projectionContext.setLoadedObject(shadow); - projectionContext.setExists(ShadowUtil.isExists(shadow.asObjectable())); - } catch (ObjectNotFoundException e) { - if (refVal.getObject() == null) { - // account does not exist, no composite account in - // ref -> this is really an error - throw e; - } else { - // New account (with OID) - result.muteLastSubresultError(); - shadow = refVal.getObject(); - if (!shadow.hasCompleteDefinition()) { - provisioningService.applyDefinition(shadow, task, result); - } - // Create account context from embedded object - projectionContext = createProjectionContext(context, shadow, task, result); - ObjectDelta accountPrimaryDelta = shadow.createAddDelta(); - projectionContext.setPrimaryDelta(accountPrimaryDelta); - projectionContext.setFullShadow(true); - projectionContext.setExists(false); - projectionContext.setShadowExistsInRepo(false); - isCombinedAdd = true; - } - } - } - if (context.isDoReconciliationForAllProjections() && !isCombinedAdd) { - projectionContext.setDoReconciliation(true); - } - projectionContext.setFresh(true); - } - } - - if (linkRefDelta.getValuesToDelete() != null) { - for (PrismReferenceValue refVal : linkRefDelta.getValuesToDelete()) { - String oid = refVal.getOid(); - LensProjectionContext projectionContext = null; - PrismObject shadow = null; - if (oid == null) { - throw new SchemaException("Cannot delete account ref without an oid in " + focus); - } else { - try { - // Using NO_FETCH so we avoid reading in a full account. This is more efficient as we don't need full account here. - // We need to fetch from provisioning and not repository so the correct definition will be set. - Collection> options = SelectorOptions.createCollection(GetOperationOptions.createNoFetch()); - shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); - // Create account context from retrieved object - projectionContext = getOrCreateAccountContext(context, shadow, task, result); - projectionContext.setLoadedObject(shadow); - projectionContext.setExists(ShadowUtil.isExists(shadow.asObjectable())); - } catch (ObjectNotFoundException e) { - try{ - // Broken accountRef. We need to try again with raw options, because the error should be thrown because of non-existent resource - Collection> options = SelectorOptions.createCollection(GetOperationOptions.createRaw()); - shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); - projectionContext = getOrCreateEmptyThombstoneProjectionContext(context, oid); - projectionContext.setFresh(true); - projectionContext.setExists(false); - projectionContext.setShadowExistsInRepo(false); - OperationResult getObjectSubresult = result.getLastSubresult(); - getObjectSubresult.setErrorsHandled(); - } catch (ObjectNotFoundException ex){ - // This is still OK. It means deleting an accountRef - // that points to non-existing object - // just log a warning - LOGGER.warn("Deleting accountRef of " + focus + " that points to non-existing OID " - + oid); - } - - } - } - if (projectionContext != null) { - if (refVal.getObject() == null) { - projectionContext.setSynchronizationIntent(SynchronizationIntent.UNLINK); - } else { - projectionContext.setSynchronizationIntent(SynchronizationIntent.DELETE); - ObjectDelta accountPrimaryDelta = shadow.createDeleteDelta(); - projectionContext.setPrimaryDelta(accountPrimaryDelta); - } - projectionContext.setFresh(true); - } - - } - } - - // remove the accountRefs without oid. These will get into the way now. - // The accounts - // are in the context now and will be linked at the end of the process - // (it they survive the policy) - // We need to make sure this happens on the real primary focus delta - - ObjectDelta primaryDeltaToUpdate; - if (focusPrimaryDelta.isImmutable()) { - primaryDeltaToUpdate = focusPrimaryDelta.clone(); - focusContext.setPrimaryDelta(primaryDeltaToUpdate); - } else { - primaryDeltaToUpdate = focusPrimaryDelta; - } - - if (primaryDeltaToUpdate.getChangeType() == ChangeType.ADD) { - primaryDeltaToUpdate.getObjectToAdd().removeReference(FocusType.F_LINK_REF); - } else if (primaryDeltaToUpdate.getChangeType() == ChangeType.MODIFY) { - primaryDeltaToUpdate.removeReferenceModification(FocusType.F_LINK_REF); - } - // It is little bit questionable whether we need to make primary delta immutable. It makes some sense, but I am not sure. - // Note that (as a side effect) this can make "focus new" immutable as well, in the case of ADD delta. - primaryDeltaToUpdate.freeze(); - } - - private void loadProjectionContextsSync(LensContext context, Task task, OperationResult result) throws SchemaException, - ObjectNotFoundException, CommunicationException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - for (LensProjectionContext projCtx : context.getProjectionContexts()) { - if (projCtx.isFresh() && projCtx.getObjectCurrent() != null) { - // already loaded - continue; - } - ObjectDelta syncDelta = projCtx.getSyncDelta(); - if (syncDelta != null) { - if (projCtx.isDoReconciliation()) { - // Do not load old account now. It will get loaded later in the - // reconciliation step. Just mark it as fresh. - projCtx.setFresh(true); - continue; - } - String oid = syncDelta.getOid(); - PrismObject shadow = null; - - if (syncDelta.getChangeType() == ChangeType.ADD) { - shadow = syncDelta.getObjectToAdd().clone(); - projCtx.setLoadedObject(shadow); - projCtx.setExists(ShadowUtil.isExists(shadow.asObjectable())); - - } else { - - if (oid == null) { - throw new IllegalArgumentException("No OID in sync delta in " + projCtx); - } - // Using NO_FETCH so we avoid reading in a full account. This is more efficient as we don't need full account here. - // We need to fetch from provisioning and not repository so the correct definition will be set. - GetOperationOptions option = GetOperationOptions.createNoFetch(); - option.setDoNotDiscovery(true); - option.setPointInTimeType(PointInTimeType.FUTURE); - Collection> options = SelectorOptions.createCollection(option); - - try { - - shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); - - } catch (ObjectNotFoundException e) { - LOGGER.trace("Loading shadow {} from sync delta failed: not found", oid); - projCtx.setExists(false); - projCtx.setObjectCurrent(null); - projCtx.setShadowExistsInRepo(false); - } - - // We will not set old account if the delta is delete. The - // account does not really exists now. - // (but the OID and resource will be set from the repo - // shadow) - if (syncDelta.getChangeType() == ChangeType.DELETE) { - projCtx.markTombstone(); - } else if (shadow != null) { - syncDelta.applyTo(shadow); - projCtx.setLoadedObject(shadow); - projCtx.setExists(ShadowUtil.isExists(shadow.asObjectable())); - } - } - - // Make sure OID is set correctly - projCtx.setOid(oid); - // Make sure that resource is also resolved - if (projCtx.getResource() == null && shadow != null) { - String resourceOid = ShadowUtil.getResourceOid(shadow.asObjectable()); - if (resourceOid == null) { - throw new IllegalArgumentException("No resource OID in " + shadow); - } - ResourceType resourceType = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result); - projCtx.setResource(resourceType); - } - projCtx.setFresh(true); - } - } - } - -// private boolean canBeLoaded(LensContext context, LensProjectionContext projCtx){ -// if (QNameUtil.qNameToUri(SchemaConstants.CHANGE_CHANNEL_DISCOVERY).equals(context.getChannel()) && projCtx == null && ModelExecuteOptions.isLimitPropagation(context.getOptions())) { -// // avoid to create projection context if the channel which -// // triggered this operation is discovery..we need only -// // projection context of discovered shadow -// return false; -// } -// return true; -// } - - private LensProjectionContext getOrCreateAccountContext(LensContext context, - PrismObject projection, Task task, OperationResult result) throws ObjectNotFoundException, - CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { - ShadowType shadowType = projection.asObjectable(); - String resourceOid = ShadowUtil.getResourceOid(shadowType); - if (resourceOid == null) { - throw new SchemaException("The " + projection + " has null resource reference OID"); - } - - LensProjectionContext projectionContext = context.findProjectionContextByOid(shadowType.getOid()); - - if (projectionContext == null) { - String intent = ShadowUtil.getIntent(shadowType); - ShadowKindType kind = ShadowUtil.getKind(shadowType); - ResourceType resource = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result); - intent = LensUtil.refineProjectionIntent(kind, intent, resource, prismContext); - boolean thombstone = false; - if (ShadowUtil.isDead(shadowType)) { - thombstone = true; - } - ResourceShadowDiscriminator rsd = new ResourceShadowDiscriminator(resourceOid, kind, intent, shadowType.getTag(), thombstone); - projectionContext = LensUtil.getOrCreateProjectionContext(context, rsd); - - if (projectionContext.getOid() == null) { - projectionContext.setOid(projection.getOid()); - } else if (projection.getOid() != null && !projectionContext.getOid().equals(projection.getOid())) { - // Conflict. We have existing projection and another project that is added (with the same discriminator). - // Chances are that the old object is already deleted (e.g. during rename). So let's be - // slightly inefficient here and check for existing shadow existence - try { - GetOperationOptions rootOpt = GetOperationOptions.createPointInTimeType(PointInTimeType.FUTURE); - rootOpt.setDoNotDiscovery(true); - Collection> opts = SelectorOptions.createCollection(rootOpt); - LOGGER.trace("Projection conflict detected, existing: {}, new {}", projectionContext.getOid(), projection.getOid()); - PrismObject existingShadow = provisioningService.getObject(ShadowType.class, projectionContext.getOid(), opts, task, result); - // Maybe it is the other way around - try { - PrismObject newShadow = provisioningService.getObject(ShadowType.class, projection.getOid(), opts, task, result); - // Obviously, two projections with the same discriminator exists - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Projection {} already exists in context\nExisting:\n{}\nNew:\n{}", rsd, - existingShadow.debugDump(1), newShadow.debugDump(1)); - } - if (!ShadowUtil.isDead(newShadow.asObjectable())) { - throw new PolicyViolationException("Projection "+rsd+" already exists in context (existing "+existingShadow+", new "+projection); - } - // Dead shadow. This is somehow expected, fix it and we can go on - rsd.setTombstone(true); - projectionContext = LensUtil.getOrCreateProjectionContext(context, rsd); - projectionContext.setExists(ShadowUtil.isExists(newShadow.asObjectable())); - projectionContext.setFullShadow(false); - } catch (ObjectNotFoundException e) { - // This is somehow expected, fix it and we can go on - result.muteLastSubresultError(); - // We have to create new context in this case, but it has to have thumbstone set - rsd.setTombstone(true); - projectionContext = LensUtil.getOrCreateProjectionContext(context, rsd); - // We have to mark it as dead right now, otherwise the uniqueness check may fail - markShadowDead(projection.getOid(), result); - projectionContext.setShadowExistsInRepo(false); - } - } catch (ObjectNotFoundException e) { - // This is somehow expected, fix it and we can go on - result.muteLastSubresultError(); - String shadowOid = projectionContext.getOid(); - projectionContext.getResourceShadowDiscriminator().setTombstone(true); - projectionContext = LensUtil.getOrCreateProjectionContext(context, rsd); - projectionContext.setShadowExistsInRepo(false); - // We have to mark it as dead right now, otherwise the uniqueness check may fail - markShadowDead(shadowOid, result); - } - } - } - return projectionContext; - } - - private void markShadowDead(String oid, OperationResult result) { - if (oid == null) { - // nothing to mark - return; - } - Collection> modifications = MiscSchemaUtil.createCollection( - prismContext.deltaFactory().property().createReplaceDelta(prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(ShadowType.class), - ShadowType.F_DEAD, true)); - try { - cacheRepositoryService.modifyObject(ShadowType.class, oid, modifications, result); - // TODO report to task? - } catch (ObjectNotFoundException e) { - // Done already - result.muteLastSubresultError(); - } catch (ObjectAlreadyExistsException | SchemaException e) { - // Should not happen - throw new SystemException(e.getMessage(), e); - } - } - - - private LensProjectionContext createProjectionContext(LensContext context, - PrismObject account, Task task, OperationResult result) throws ObjectNotFoundException, - CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - ShadowType shadowType = account.asObjectable(); - String resourceOid = ShadowUtil.getResourceOid(shadowType); - if (resourceOid == null) { - throw new SchemaException("The " + account + " has null resource reference OID"); - } - String intent = ShadowUtil.getIntent(shadowType); - ShadowKindType kind = ShadowUtil.getKind(shadowType); - ResourceType resource = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result); - String accountIntent = LensUtil.refineProjectionIntent(kind, intent, resource, prismContext); - ResourceShadowDiscriminator rsd = new ResourceShadowDiscriminator(resourceOid, kind, accountIntent, shadowType.getTag(), false); - LensProjectionContext accountSyncContext = context.findProjectionContext(rsd); - if (accountSyncContext != null) { - throw new SchemaException("Attempt to add "+account+" to a focus that already contains projection of type '"+accountIntent+"' on "+resource); - } - accountSyncContext = context.createProjectionContext(rsd); - accountSyncContext.setResource(resource); - accountSyncContext.setOid(account.getOid()); - return accountSyncContext; - } - - private LensProjectionContext findAccountContext(String accountOid, LensContext context) { - for (LensProjectionContext accContext : context.getProjectionContexts()) { - if (accountOid.equals(accContext.getOid())) { - return accContext; - } - } - - return null; - } - - private LensProjectionContext getOrCreateEmptyThombstoneProjectionContext(LensContext context, - String missingShadowOid) { - LensProjectionContext projContext = context.findProjectionContextByOid(missingShadowOid); - if (projContext == null) { - projContext = context.createProjectionContext(null); - projContext.setOid(missingShadowOid); - } - - if (projContext.getResourceShadowDiscriminator() == null) { - projContext.setResourceShadowDiscriminator(new ResourceShadowDiscriminator(null, null, null, null, true)); - } else { - projContext.markTombstone(); - } - - projContext.setFullShadow(false); - projContext.setObjectCurrent(null); - - return projContext; - } - - /** - * Check reconcile flag in account sync context and set accountOld - * variable if it's not set (from provisioning), load resource (if not set already), etc. - */ - private void finishLoadOfProjectionContext(LensContext context, - LensProjectionContext projContext, Task task, OperationResult result) - throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, - SecurityViolationException, ExpressionEvaluationException { - - if (projContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { - return; - } - - // MID-2436 (volatile objects) - as a quick but effective hack, we set reconciliation:=TRUE for volatile accounts - ResourceObjectTypeDefinitionType objectDefinition = projContext.getResourceObjectTypeDefinitionType(); - if (objectDefinition != null && objectDefinition.getVolatility() == ResourceObjectVolatilityType.UNPREDICTABLE && !projContext.isDoReconciliation()) { - LOGGER.trace("Resource object volatility is UNPREDICTABLE => setting doReconciliation to TRUE for {}", projContext.getResourceShadowDiscriminator()); - projContext.setDoReconciliation(true); - } - - // Remember OID before the object could be wiped - String projectionObjectOid = projContext.getOid(); - if (projContext.isDoReconciliation() && !projContext.isFullShadow()) { - // The current object is useless here. So lets just wipe it so it will get loaded - projContext.setObjectCurrent(null); - } - - // Load current object - boolean tombstone = false; - PrismObject projectionObject = projContext.getObjectCurrent(); - if (projContext.getObjectCurrent() == null || needToReload(context, projContext)) { - if (projContext.isAdd()) { - // No need to load old object, there is none - projContext.setExists(false); - projContext.recompute(); - projectionObject = projContext.getObjectNew(); - } else { - if (projectionObjectOid == null) { - projContext.setExists(false); - if (projContext.getResourceShadowDiscriminator() == null || projContext.getResourceShadowDiscriminator().getResourceOid() == null) { - throw new SystemException( - "Projection "+projContext.getHumanReadableName()+" with null OID, no representation and no resource OID in account sync context "+projContext); - } - } else { - GetOperationOptions rootOptions = GetOperationOptions.createPointInTimeType(PointInTimeType.FUTURE); - if (projContext.isDoReconciliation()) { - rootOptions.setForceRefresh(true); - if (SchemaConstants.CHANGE_CHANNEL_DISCOVERY_URI.equals(context.getChannel())) { - // Avoid discovery loops - rootOptions.setDoNotDiscovery(true); - } - } else { - rootOptions.setNoFetch(true); - } - rootOptions.setAllowNotFound(true); - Collection> options = SelectorOptions.createCollection(rootOptions); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Loading shadow {} for projection {}, options={}", projectionObjectOid, projContext.getHumanReadableName(), options); - } - - try { - PrismObject objectOld = provisioningService.getObject( - projContext.getObjectTypeClass(), projectionObjectOid, options, task, result); - if (LOGGER.isTraceEnabled()) { - if (!GetOperationOptions.isNoFetch(rootOptions) && !GetOperationOptions.isRaw(rootOptions)) { - LOGGER.trace("Full shadow loaded for {}:\n{}", projContext.getHumanReadableName(), objectOld.debugDump(1)); - } - } - Validate.notNull(objectOld.getOid()); - if (InternalsConfig.consistencyChecks) { - String resourceOid = projContext.getResourceOid(); - if (resourceOid != null && !resourceOid.equals(objectOld.asObjectable().getResourceRef().getOid())) { - throw new IllegalStateException("Loaded shadow with wrong resourceRef. Loading shadow "+projectionObjectOid+", got "+ - objectOld.getOid()+", expected resourceRef "+resourceOid+", but was "+objectOld.asObjectable().getResourceRef().getOid()+ - " for context "+projContext.getHumanReadableName()); - } - } - projContext.setLoadedObject(objectOld); - if (projContext.isDoReconciliation()) { - projContext.determineFullShadowFlag(objectOld); - } else { - projContext.setFullShadow(false); - } - projectionObject = objectOld; - if (ShadowUtil.isExists(objectOld.asObjectable())) { - projContext.setExists(true); - } else { - projContext.setExists(false); - if (ShadowUtil.isDead(objectOld.asObjectable())) { - projContext.markTombstone(); - } - LOGGER.debug("Foud only dead {} for projection context {}.", objectOld, projContext.getHumanReadableName()); - tombstone = true; - } - - } catch (ObjectNotFoundException ex) { - LOGGER.debug("Could not find object with oid {} for projection context {}.", projectionObjectOid, projContext.getHumanReadableName()); - // This does not mean BROKEN. The projection was there, but it gone now. - // Consistency mechanism might have kicked in and fixed the shadow. - // What we really want here is a thombstone projection or a refreshed projection. - result.muteLastSubresultError(); - projContext.setShadowExistsInRepo(false); - refreshContextAfterShadowNotFound(context, projContext, options, task, result); - - } catch (CommunicationException | SchemaException | ConfigurationException | SecurityViolationException - | RuntimeException | Error e) { - - LOGGER.warn("Problem while getting object with oid {}. Projection context {} is marked as broken: {}: {}", - projectionObjectOid, projContext.getHumanReadableName(), e.getClass().getSimpleName(), e.getMessage()); - projContext.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.BROKEN); - - ResourceType resourceType = projContext.getResource(); - if (resourceType == null) { - throw e; - } else { - ErrorSelectorType errorSelector = null; - if (resourceType.getConsistency() != null) { - errorSelector = resourceType.getConsistency().getConnectorErrorCriticality(); - } - if (errorSelector == null) { - if (e instanceof SchemaException) { - // Just continue evaluation. The error is recorded in the result. - // The consistency mechanism has (most likely) already done the best. - // We cannot do any better. - return; - } else { - throw e; - } - } else { - if (CriticalityType.FATAL.equals(ExceptionUtil.getCriticality(errorSelector, e, CriticalityType.FATAL))) { - throw e; - } else { - return; - } - } - } - } - - } - projContext.setFresh(true); - } - } else { - projectionObject = projContext.getObjectCurrent(); - if (projectionObjectOid != null) { - projContext.setExists(ShadowUtil.isExists(projectionObject.asObjectable())); - } - } - - - // Determine Resource - ResourceType resourceType = projContext.getResource(); - String resourceOid = null; - if (resourceType == null) { - if (projectionObject != null) { - ShadowType shadowType = projectionObject.asObjectable(); - resourceOid = ShadowUtil.getResourceOid(shadowType); - } else if (projContext.getResourceShadowDiscriminator() != null) { - resourceOid = projContext.getResourceShadowDiscriminator().getResourceOid(); - } else if (!tombstone) { - throw new IllegalStateException("No shadow, no discriminator and not tombstone? That won't do. Projection "+projContext.getHumanReadableName()); - } - } else { - resourceOid = resourceType.getOid(); - } - - // Determine discriminator - ResourceShadowDiscriminator discr = projContext.getResourceShadowDiscriminator(); - if (discr == null) { - if (projectionObject != null) { - ShadowType accountShadowType = projectionObject.asObjectable(); - String intent = ShadowUtil.getIntent(accountShadowType); - ShadowKindType kind = ShadowUtil.getKind(accountShadowType); - discr = new ResourceShadowDiscriminator(resourceOid, kind, intent, accountShadowType.getTag(), tombstone); - } else { - discr = new ResourceShadowDiscriminator(null, null, null, null, tombstone); - } - projContext.setResourceShadowDiscriminator(discr); - } else { - if (tombstone) { - // We do not want to reset tombstone flag if it was set before - projContext.markTombstone(); - } - } - - // Load resource - if (resourceType == null && resourceOid != null) { - resourceType = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result); - projContext.setResource(resourceType); - } - - //Determine refined schema and password policies for account type - RefinedObjectClassDefinition structuralObjectClassDef = projContext.getStructuralObjectClassDefinition(); - if (structuralObjectClassDef != null) { - LOGGER.trace("Finishing loading of projection context: security policy"); - SecurityPolicyType projectionSecurityPolicy = securityHelper.locateProjectionSecurityPolicy(projContext.getStructuralObjectClassDefinition(), task, result); - LOGGER.trace("Located security policy for: {},\n {}", projContext, projectionSecurityPolicy); - projContext.setProjectionSecurityPolicy(projectionSecurityPolicy); - } else { - LOGGER.trace("No structural object class definition, skipping determining security policy"); - } - - //set limitation, e.g. if this projection context should be recomputed and processed by projector - if (ModelExecuteOptions.isLimitPropagation(context.getOptions())){ - if (context.getTriggeredResourceOid() != null){ - if (!context.getTriggeredResourceOid().equals(resourceOid)){ - projContext.setCanProject(false); - } - } - } - - setPrimaryDeltaOldValue(projContext); - } - - private boolean needToReload(LensContext context, - LensProjectionContext projContext) { - ResourceShadowDiscriminator discr = projContext.getResourceShadowDiscriminator(); - if (discr == null) { - return false; - } - // This is kind of brutal. But effective. We are reloading all higher-order dependencies - // before they are processed. This makes sure we have fresh state when they are re-computed. - // Because higher-order dependencies may have more than one projection context and the - // changes applied to one of them are not automatically reflected on on other. therefore we need to reload. - if (discr.getOrder() == 0) { - return false; - } - int executionWave = context.getExecutionWave(); - int projCtxWave = projContext.getWave(); - if (executionWave == projCtxWave - 1) { - // Reload right before its execution wave - return true; - } - return false; - } - - private void fullCheckConsistence(LensContext context) { - context.checkConsistence(); - for (LensProjectionContext projectionContext: context.getProjectionContexts()) { - if (projectionContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { - continue; - } - if (projectionContext.getResourceShadowDiscriminator() == null) { - throw new IllegalStateException("No discriminator in "+projectionContext); - } - } - } - - public void loadFullShadow(LensContext context, LensProjectionContext projCtx, String reason, Task task, OperationResult parentResult) - throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - if (projCtx.isFullShadow()) { - // already loaded - return; - } - if (projCtx.isAdd() && projCtx.getOid() == null) { - // nothing to load yet - return; - } - if (projCtx.isTombstone()) { - // loading is futile - return; - } - OperationResult result = parentResult.subresult(CLASS_DOT + "loadFullShadow") - .setMinor() - .build(); - FullShadowLoadedTraceType trace; - if (result.isTraced()) { - trace = new FullShadowLoadedTraceType(prismContext); - if (result.isTracingNormal(FullShadowLoadedTraceType.class)) { - trace.setInputLensContextText(context.debugDump()); - } - trace.setInputLensContext(context.toLensContextType(getExportType(trace, result))); - result.addTrace(trace); - } else { - trace = null; - } - try { - ResourceShadowDiscriminator discr = projCtx.getResourceShadowDiscriminator(); - if (discr != null && discr.getOrder() > 0) { - // It may be just too early to load the projection - if (LensUtil.hasLowerOrderContext(context, projCtx) && (context.getExecutionWave() < projCtx.getWave())) { - // We cannot reliably load the context now - result.addReturn(DEFAULT, "too early"); - return; - } - } - - GetOperationOptions getOptions = GetOperationOptions.createAllowNotFound(); - getOptions.setPointInTimeType(PointInTimeType.FUTURE); - if (projCtx.isDoReconciliation()) { - getOptions.setForceRefresh(true); - } - if (SchemaConstants.CHANGE_CHANNEL_DISCOVERY_URI.equals(context.getChannel())) { - LOGGER.trace("Loading full resource object {} from provisioning - with doNotDiscover to avoid loops; reason: {}", - projCtx, reason); - // Avoid discovery loops - getOptions.setDoNotDiscovery(true); - } else { - LOGGER.trace("Loading full resource object {} from provisioning (discovery enabled), reason: {}, channel: {}", - projCtx, reason, context.getChannel()); - } - Collection> options = SelectorOptions.createCollection(getOptions); - applyAttributesToGet(projCtx, options); - try { - PrismObject objectCurrent = provisioningService - .getObject(ShadowType.class, projCtx.getOid(), options, task, result); - Validate.notNull(objectCurrent.getOid()); - // TODO: use setLoadedObject() instead? - projCtx.setObjectCurrent(objectCurrent); - projCtx.determineFullShadowFlag(objectCurrent); - if (ShadowUtil.isExists(objectCurrent.asObjectable())) { - result.addReturn(DEFAULT, "found"); - } else { - LOGGER.debug("Load of full resource object {} ended with non-existent shadow (options={})", projCtx, - getOptions); - projCtx.setExists(false); - refreshContextAfterShadowNotFound(context, projCtx, options, task, result); - result.addReturn(DEFAULT, "not found"); - } - - } catch (ObjectNotFoundException ex) { - LOGGER.debug("Load of full resource object {} ended with ObjectNotFoundException (options={})", projCtx, - getOptions); - result.muteLastSubresultError(); - projCtx.setShadowExistsInRepo(false); - refreshContextAfterShadowNotFound(context, projCtx, options, task, result); - result.addReturn(DEFAULT, "not found"); - } - - projCtx.recompute(); - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Loaded full resource object:\n{}", projCtx.debugDump(1)); - } - } catch (Throwable t) { - result.recordFatalError(t); - throw t; - } finally { - if (trace != null) { - if (result.isTracingNormal(FullShadowLoadedTraceType.class)) { - trace.setOutputLensContextText(context.debugDump()); - } - trace.setOutputLensContext(context.toLensContextType(getExportType(trace, result))); - } - result.computeStatusIfUnknown(); - } - } - - public void refreshContextAfterShadowNotFound(LensContext context, LensProjectionContext projCtx, Collection> options, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, ExpressionEvaluationException { - if (projCtx.isDelete()){ - //this is OK, shadow was deleted, but we will continue in processing with old shadow..and set it as full so prevent from other full loading - projCtx.setFullShadow(true); - return; - } - - boolean compensated = false; - if (!GetOperationOptions.isDoNotDiscovery(SelectorOptions.findRootOptions(options))) { - // The account might have been re-created by the discovery. - // Reload focus, try to find out if there is a new matching link (and the old is gone) - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext != null) { - Class focusClass = focusContext.getObjectTypeClass(); - if (FocusType.class.isAssignableFrom(focusClass)) { - LOGGER.trace("Reloading focus to check for new links"); - PrismObject focusCurrent; - try { - focusCurrent = cacheRepositoryService.getObject(focusContext.getObjectTypeClass(), focusContext.getOid(), null, result); - } catch (ObjectNotFoundException e) { - if (focusContext.isDelete()) { - // This may be OK. This may be later wave and the focus may be already deleted. - // Therefore let's just keep what we have. We have no way how to refresh context - // in that situation. - result.muteLastSubresultError(); - LOGGER.trace("ObjectNotFound error is not compensated (focus already deleted), setting context to tombstone"); - projCtx.markTombstone(); - return; - } else { - throw e; - } - } - FocusType focusType = (FocusType) focusCurrent.asObjectable(); - for (ObjectReferenceType linkRef: focusType.getLinkRef()) { - if (linkRef.getOid().equals(projCtx.getOid())) { - // The deleted shadow is still in the linkRef. This should not happen, but it obviously happens sometimes. - // Maybe some strange race condition? Anyway, we want a robust behavior and this linkRef should NOT be there. - // So simple remove it. - LOGGER.warn("The OID "+projCtx.getOid()+" of deleted shadow still exists in the linkRef after discovery ("+focusCurrent+"), removing it"); - ReferenceDelta unlinkDelta = prismContext.deltaFactory().reference().createModificationDelete( - FocusType.F_LINK_REF, focusContext.getObjectDefinition(), linkRef.asReferenceValue().clone()); - focusContext.swallowToSecondaryDelta(unlinkDelta); - continue; - } - boolean found = false; - for (LensProjectionContext pCtx: context.getProjectionContexts()) { - if (linkRef.getOid().equals(pCtx.getOid())) { - found = true; - break; - } - } - if (!found) { - // This link is new, it is not in the existing lens context - PrismObject newLinkRepoShadow = cacheRepositoryService.getObject(ShadowType.class, linkRef.getOid(), null, result); - if (ShadowUtil.matches(newLinkRepoShadow, projCtx.getResourceShadowDiscriminator())) { - LOGGER.trace("Found new matching link: {}, updating projection context", newLinkRepoShadow); - LOGGER.trace("Applying definition from provisioning first."); // MID-3317 - provisioningService.applyDefinition(newLinkRepoShadow, task, result); - projCtx.setObjectCurrent(newLinkRepoShadow); - projCtx.setOid(newLinkRepoShadow.getOid()); - projCtx.recompute(); - compensated = true; - break; - } else { - LOGGER.trace("Found new link: {}, but skipping it because it does not match the projection context", newLinkRepoShadow); - } - } - } - } - } - - } - - if (!compensated) { - LOGGER.trace("ObjectNotFound error is not compensated, setting context to tombstone"); - projCtx.markTombstone(); - } - } - - private void applyAttributesToGet(LensProjectionContext projCtx, Collection> options) throws SchemaException { - if ( !LensUtil.isPasswordReturnedByDefault(projCtx) - && LensUtil.needsFullShadowForCredentialProcessing(projCtx)) { - options.add(SelectorOptions.create(prismContext.toUniformPath(SchemaConstants.PATH_PASSWORD_VALUE), GetOperationOptions.createRetrieve())); - } - } - - public void reloadSecurityPolicyIfNeeded(@NotNull LensContext context, - @NotNull LensFocusContext focusContext, Task task, OperationResult result) - throws ExpressionEvaluationException, SchemaException, - CommunicationException, ConfigurationException, SecurityViolationException { - if (focusContext.hasOrganizationalChange()) { - loadSecurityPolicy(context, true, task, result); - } - } - - private void loadSecurityPolicy(LensContext context, - Task task, OperationResult result) throws ExpressionEvaluationException, - SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - loadSecurityPolicy(context, false, task, result); - } - - @SuppressWarnings("unchecked") - private void loadSecurityPolicy(LensContext context, boolean forceReload, - Task task, OperationResult result) throws ExpressionEvaluationException, - SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { - LensFocusContext genericFocusContext = context.getFocusContext(); - if (genericFocusContext == null || !genericFocusContext.represents(FocusType.class)) { - LOGGER.trace("Skipping load of security policy because focus is not of FocusType"); - return; - } - LensFocusContext focusContext = (LensFocusContext) genericFocusContext; - PrismObject focus = focusContext.getObjectAny(); - SecurityPolicyType globalSecurityPolicy = determineAndSetGlobalSecurityPolicy(context, focus, task, result); - SecurityPolicyType focusSecurityPolicy = determineAndSetFocusSecurityPolicy(focusContext, focus, globalSecurityPolicy, - forceReload, task, result); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Security policy:\n Global:\n{}\n Focus:\n{}", - globalSecurityPolicy.asPrismObject().debugDump(2), - focusSecurityPolicy==null?null:focusSecurityPolicy.asPrismObject().debugDump(2)); - } else { - LOGGER.debug("Security policy: global: {}, focus: {}", globalSecurityPolicy, focusSecurityPolicy); - } - } - - @NotNull - private SecurityPolicyType determineAndSetGlobalSecurityPolicy(LensContext context, - PrismObject focus, Task task, OperationResult result) - throws CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - SecurityPolicyType existingPolicy = context.getGlobalSecurityPolicy(); - if (existingPolicy != null) { - return existingPolicy; - } else { - SecurityPolicyType loadedPolicy = securityHelper.locateGlobalSecurityPolicy(focus, context.getSystemConfiguration(), - task, result); - SecurityPolicyType resultingPolicy; - if (loadedPolicy != null) { - resultingPolicy = loadedPolicy; - } else { - // use empty policy to avoid repeated lookups - resultingPolicy = new SecurityPolicyType(); - } - context.setGlobalSecurityPolicy(resultingPolicy); - return resultingPolicy; - } - } - - private SecurityPolicyType determineAndSetFocusSecurityPolicy(LensFocusContext focusContext, - PrismObject focus, SecurityPolicyType globalSecurityPolicy, boolean forceReload, Task task, - OperationResult result) throws SchemaException { - SecurityPolicyType existingPolicy = focusContext.getSecurityPolicy(); - if (existingPolicy != null && !forceReload) { - return existingPolicy; - } else { - SecurityPolicyType loadedPolicy = securityHelper.locateFocusSecurityPolicy(focus, task, result); - SecurityPolicyType resultingPolicy; - if (loadedPolicy != null) { - resultingPolicy = loadedPolicy; - } else { - // Not very clean. In fact we should store focus security policy separate from global - // policy to avoid confusion. But need to do this to fix MID-4793 and backport the fix. - // Therefore avoiding big changes. TODO: fix properly later - resultingPolicy = globalSecurityPolicy; - } - focusContext.setSecurityPolicy(resultingPolicy); - return resultingPolicy; - } - } -} +/* + * Copyright (c) 2010-2018 Evolveum and contributors + * + * This work is dual-licensed under the Apache License 2.0 + * and European Union Public License. See LICENSE file for details. + */ +package com.evolveum.midpoint.model.impl.lens.projector; + +import static com.evolveum.midpoint.model.impl.lens.LensUtil.getExportType; +import static com.evolveum.midpoint.schema.internals.InternalsConfig.consistencyChecks; +import static com.evolveum.midpoint.schema.result.OperationResult.DEFAULT; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.*; +import com.evolveum.midpoint.schema.*; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.internals.InternalsConfig; +import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.Validate; +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.refinery.RefinedObjectClassDefinition; +import com.evolveum.midpoint.model.api.ModelExecuteOptions; +import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; +import com.evolveum.midpoint.model.common.ArchetypeManager; +import com.evolveum.midpoint.model.common.SystemObjectCache; +import com.evolveum.midpoint.model.impl.lens.ClockworkMedic; +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.model.impl.lens.LensElementContext; +import com.evolveum.midpoint.model.impl.lens.LensFocusContext; +import com.evolveum.midpoint.model.impl.lens.LensObjectDeltaOperation; +import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; +import com.evolveum.midpoint.model.impl.lens.LensUtil; +import com.evolveum.midpoint.model.api.context.SynchronizationIntent; +import com.evolveum.midpoint.model.impl.security.SecurityHelper; +import com.evolveum.midpoint.provisioning.api.ProvisioningService; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ExceptionUtil; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.schema.util.ShadowUtil; +import com.evolveum.midpoint.task.api.Task; +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.ObjectAlreadyExistsException; +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.exception.SystemException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; + +/** + * Context loader loads the missing parts of the context. The context enters the projector with just the minimum information. + * Context loader gets missing data such as accounts. It gets them from the repository or provisioning as necessary. It follows + * the account links in focus (linkRef) and focus deltas. + * + * @author Radovan Semancik + * + */ +@Component +public class ContextLoader { + + @Autowired + @Qualifier("cacheRepositoryService") + private transient RepositoryService cacheRepositoryService; + + @Autowired private SystemObjectCache systemObjectCache; + @Autowired private ArchetypeManager archetypeManager; + @Autowired private ProvisioningService provisioningService; + @Autowired private PrismContext prismContext; + @Autowired private SecurityHelper securityHelper; + @Autowired private ClockworkMedic medic; + + private static final Trace LOGGER = TraceManager.getTrace(ContextLoader.class); + + public static final String CLASS_DOT = ContextLoader.class.getName() + "."; + private static final String OPERATION_LOAD = CLASS_DOT + "load"; + private static final String OPERATION_LOAD_PROJECTION = CLASS_DOT + "loadProjection"; + + public void load(LensContext context, String activityDescription, + Task task, OperationResult parentResult) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { + + context.checkAbortRequested(); + + context.recompute(); + + OperationResult result = parentResult.createMinorSubresult(OPERATION_LOAD); + ProjectorComponentTraceType trace; + if (result.isTraced()) { + trace = new ProjectorComponentTraceType(prismContext); + if (result.isTracingNormal(ProjectorComponentTraceType.class)) { + trace.setInputLensContextText(context.debugDump()); + } + trace.setInputLensContext(context.toLensContextType(getExportType(trace, result))); + result.addTrace(trace); + } else { + trace = null; + } + + try { + + for (LensProjectionContext projectionContext: context.getProjectionContexts()) { + preprocessProjectionContext(context, projectionContext, task, result); + } + + if (consistencyChecks) context.checkConsistence(); + + determineFocusContext(context, task, result); + + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext != null) { + + context.recomputeFocus(); + + loadFromSystemConfig(context, task, result); + + if (FocusType.class.isAssignableFrom(context.getFocusClass())) { + // this also removes the accountRef deltas + //noinspection unchecked + loadLinkRefs((LensContext)context, task, result); + LOGGER.trace("loadLinkRefs done"); + } + + // Some cleanup + if (focusContext.getPrimaryDelta() != null && focusContext.getPrimaryDelta().isModify() && focusContext.getPrimaryDelta().isEmpty()) { + focusContext.setPrimaryDelta(null); + } + + for (LensProjectionContext projectionContext: context.getProjectionContexts()) { + if (projectionContext.getSynchronizationIntent() != null) { + // Accounts with explicitly set intent are never rotten. These are explicitly requested actions + // if they fail then they really should fail. + projectionContext.setFresh(true); + } + } + + setPrimaryDeltaOldValue(focusContext); + + } else { + // Projection contexts are not rotten in this case. There is no focus so there is no way to refresh them. + for (LensProjectionContext projectionContext: context.getProjectionContexts()) { + projectionContext.setFresh(true); + } + } + + removeRottenContexts(context); + + if (consistencyChecks) context.checkConsistence(); + + for (LensProjectionContext projectionContext: context.getProjectionContexts()) { + context.checkAbortRequested(); + // TODO: not perfect. Practically, we want loadProjection operation to contain all the projection + // results. But for that we would need code restructure. + OperationResult projectionResult = result.createMinorSubresult(OPERATION_LOAD_PROJECTION); + try { + finishLoadOfProjectionContext(context, projectionContext, task, projectionResult); + } catch (Throwable e) { + projectionResult.recordFatalError(e); + throw e; + } + projectionResult.computeStatus(); + } + + if (consistencyChecks) context.checkConsistence(); + + context.recompute(); + + if (consistencyChecks) { + fullCheckConsistence(context); + } + + medic.traceContext(LOGGER, activityDescription, "after load", false, context, false); + + result.computeStatusComposite(); + + } catch (Throwable e) { + result.recordFatalError(e); + throw e; + } finally { + if (trace != null) { + if (result.isTracingNormal(ProjectorComponentTraceType.class)) { + trace.setOutputLensContextText(context.debugDump()); + } + trace.setOutputLensContext(context.toLensContextType(getExportType(trace, result))); + } + } + } + + + /** + * Removes projection contexts that are not fresh. + * These are usually artifacts left after the context reload. E.g. an account that used to be linked to a user before + * but was removed in the meantime. + */ + private void removeRottenContexts(LensContext context) { + Iterator projectionIterator = context.getProjectionContextsIterator(); + while (projectionIterator.hasNext()) { + LensProjectionContext projectionContext = projectionIterator.next(); + if (projectionContext.getPrimaryDelta() != null && !projectionContext.getPrimaryDelta().isEmpty()) { + // We must never remove contexts with primary delta. Even though it fails later on. + // What the user wishes should be done (or at least attempted) regardless of the consequences. + // Vox populi vox dei + continue; + } + if (projectionContext.getWave() >= context.getExecutionWave()) { + // We must not remove context from this and later execution waves. These haven't had the + // chance to be executed yet + continue; + } + ResourceShadowDiscriminator discr = projectionContext.getResourceShadowDiscriminator(); + if (discr != null && discr.getOrder() > 0) { + // HACK never rot higher-order context. TODO: check if lower-order context is rotten, the also rot this one + continue; + } + if (!projectionContext.isFresh()) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Removing rotten context {}", projectionContext.getHumanReadableName()); + } + + if (projectionContext.isToBeArchived()) { + context.getHistoricResourceObjects().add(projectionContext.getResourceShadowDiscriminator()); + } + + List> executedDeltas = projectionContext.getExecutedDeltas(); + context.getRottenExecutedDeltas().addAll(executedDeltas); + projectionIterator.remove(); + } + } + } + + + /** + * Make sure that the projection context is loaded as appropriate. + */ + public void makeSureProjectionIsLoaded(LensContext context, + LensProjectionContext projectionContext, Task task, OperationResult result) throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + preprocessProjectionContext(context, projectionContext, task, result); + finishLoadOfProjectionContext(context, projectionContext, task, result); + } + + /** + * Make sure that the context is OK and consistent. It means that is has a resource, it has correctly processed + * discriminator, etc. + */ + private void preprocessProjectionContext(LensContext context, + LensProjectionContext projectionContext, Task task, OperationResult result) + throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + if (!ShadowType.class.isAssignableFrom(projectionContext.getObjectTypeClass())) { + return; + } + String resourceOid = null; + boolean isThombstone = false; + ShadowKindType kind = ShadowKindType.ACCOUNT; + String intent = null; + String tag = null; + int order = 0; + ResourceShadowDiscriminator rsd = projectionContext.getResourceShadowDiscriminator(); + if (rsd != null) { + resourceOid = rsd.getResourceOid(); + isThombstone = rsd.isTombstone(); + kind = rsd.getKind(); + intent = rsd.getIntent(); + tag = rsd.getTag(); + order = rsd.getOrder(); + } + if (resourceOid == null && projectionContext.getObjectCurrent() != null) { + resourceOid = ShadowUtil.getResourceOid(projectionContext.getObjectCurrent().asObjectable()); + } + if (resourceOid == null && projectionContext.getObjectNew() != null) { + resourceOid = ShadowUtil.getResourceOid(projectionContext.getObjectNew().asObjectable()); + } + // We still may not have resource OID here. E.g. in case of the delete when the account is not loaded yet. It is + // perhaps safe to skip this. It will be sorted out later. + + if (resourceOid != null) { + if (intent == null && projectionContext.getObjectNew() != null) { + ShadowType shadowNewType = projectionContext.getObjectNew().asObjectable(); + kind = ShadowUtil.getKind(shadowNewType); + intent = ShadowUtil.getIntent(shadowNewType); + tag = shadowNewType.getTag(); + } + ResourceType resource = projectionContext.getResource(); + if (resource == null) { + resource = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result); + projectionContext.setResource(resource); + } + String refinedIntent = LensUtil.refineProjectionIntent(kind, intent, resource, prismContext); + rsd = new ResourceShadowDiscriminator(resourceOid, kind, refinedIntent, tag, isThombstone); + rsd.setOrder(order); + projectionContext.setResourceShadowDiscriminator(rsd); + } + if (projectionContext.getOid() == null && rsd != null && rsd.getOrder() != 0) { + // Try to determine OID from lower-order contexts + for (LensProjectionContext aProjCtx: context.getProjectionContexts()) { + ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator(); + if (rsd.equivalent(aDiscr) && aProjCtx.getOid() != null) { + projectionContext.setOid(aProjCtx.getOid()); + break; + } + } + } + } + + /** + * try to load focus context from oid, delta, projections (e.g. by determining account owners) + */ + public void determineFocusContext(LensContext context, Task task, OperationResult parentResult) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + + OperationResult result = parentResult.subresult(CLASS_DOT + "determineFocusContext") + .setMinor() + .build(); + FocusLoadedTraceType trace; + if (result.isTraced()) { + trace = new FocusLoadedTraceType(prismContext); + if (result.isTracingNormal(FocusLoadedTraceType.class)) { + trace.setInputLensContextText(context.debugDump()); + } + trace.setInputLensContext(context.toLensContextType(getExportType(trace, result))); + result.addTrace(trace); + } else { + trace = null; + } + LensFocusContext focusContext = context.getFocusContext(); + try { + if (focusContext == null) { + focusContext = determineFocusContextFromProjections(context, result); + } + + if (focusContext == null) { + result.addReturnComment("Nothing to load"); + return; + } + + // Make sure that we RELOAD the focus object if the context is not fresh + // the focus may have changed in the meantime + if (focusContext.getObjectCurrent() != null && focusContext.isFresh()) { + result.addReturnComment("Already loaded"); + return; + } + ObjectDelta objectDelta = focusContext.getDelta(); + if (objectDelta != null && objectDelta.isAdd() && focusContext.getExecutedDeltas().isEmpty()) { + //we're adding the focal object. No need to load it, it is in the delta + focusContext.setFresh(true); + result.addReturnComment("Obtained from delta"); + return; + } + if (focusContext.getObjectCurrent() != null && objectDelta != null && objectDelta.isDelete()) { + // do not reload if the delta is delete. the reload will most likely fail anyway + // but DO NOT set the fresh flag in this case, it may be misleading + result.addReturnComment("Not loading as delta is DELETE"); + return; + } + + String focusOid = focusContext.getOid(); + if (StringUtils.isBlank(focusOid)) { + throw new IllegalArgumentException("No OID in primary focus delta"); + } + + PrismObject object; + if (ObjectTypes.isClassManagedByProvisioning(focusContext.getObjectTypeClass())) { + object = provisioningService.getObject(focusContext.getObjectTypeClass(), focusOid, + SelectorOptions.createCollection(GetOperationOptions.createNoFetch()), task, result); + setLoadedFocusInTrace(object, trace); + result.addReturnComment("Loaded via provisioning"); + } else { + + // Always load a complete object here, including the not-returned-by-default properties. + // This is temporary measure to make sure that the mappings will have all they need. + // See MID-2635 + Collection> options = + SelectorOptions.createCollection(GetOperationOptions.createRetrieve(RetrieveOption.INCLUDE)); + object = cacheRepositoryService.getObject(focusContext.getObjectTypeClass(), focusOid, options, result); + setLoadedFocusInTrace(object, trace); + result.addReturnComment("Loaded from repository"); + } + + focusContext.setLoadedObject(object); + focusContext.setFresh(true); + LOGGER.trace("Focal object loaded: {}", object); + } catch (Throwable t) { + result.recordFatalError(t); + throw t; + } finally { + if (trace != null) { + if (result.isTracingNormal(FocusLoadedTraceType.class)) { + trace.setOutputLensContextText(context.debugDump()); + } + trace.setOutputLensContext(context.toLensContextType(getExportType(trace, result))); + } + result.computeStatusIfUnknown(); + } + } + + private void setLoadedFocusInTrace(PrismObject object, FocusLoadedTraceType trace) { + if (trace != null) { + trace.setFocusLoadedRef(ObjectTypeUtil.createObjectRefWithFullObject(object, prismContext)); + } + } + + private LensFocusContext determineFocusContextFromProjections(LensContext context, OperationResult result) { + String focusOid = null; + LensProjectionContext projectionContextThatYeildedFocusOid = null; + PrismObject focusOwner = null; + for (LensProjectionContext projectionContext: context.getProjectionContexts()) { + String projectionOid = projectionContext.getOid(); + if (projectionOid != null) { + PrismObject shadowOwner = cacheRepositoryService.searchShadowOwner(projectionOid, + SelectorOptions.createCollection(GetOperationOptions.createAllowNotFound()), + result); + if (shadowOwner != null) { + if (focusOid == null || focusOid.equals(shadowOwner.getOid())) { + focusOid = shadowOwner.getOid(); + //noinspection unchecked + focusOwner = (PrismObject) shadowOwner; + projectionContextThatYeildedFocusOid = projectionContext; + } else { + throw new IllegalArgumentException("The context does not have explicit focus. Attempt to determine focus failed because two " + + "projections points to different foci: "+projectionContextThatYeildedFocusOid+"->"+focusOid+"; "+ + projectionContext+"->"+shadowOwner); + } + } + } + } + + if (focusOid != null) { + LensFocusContext focusCtx = context.getOrCreateFocusContext(focusOwner.getCompileTimeClass()); + focusCtx.setOid(focusOid); + return focusCtx; + } + + return null; + } + + private void setPrimaryDeltaOldValue(LensElementContext ctx) { + if (ctx.getPrimaryDelta() != null && ctx.getObjectOld() != null && ctx.isModify()) { + boolean freezeAfterChange; + if (ctx.getPrimaryDelta().isImmutable()) { + ctx.setPrimaryDelta(ctx.getPrimaryDelta().clone()); + freezeAfterChange = true; + } else { + freezeAfterChange = false; + } + for (ItemDelta itemDelta: ctx.getPrimaryDelta().getModifications()) { + LensUtil.setDeltaOldValue(ctx, itemDelta); + } + if (freezeAfterChange) { + ctx.getPrimaryDelta().freeze(); + } + } + } + + private void loadFromSystemConfig(LensContext context, Task task, OperationResult result) + throws ObjectNotFoundException, SchemaException, ConfigurationException, ExpressionEvaluationException, CommunicationException, SecurityViolationException { + PrismObject systemConfiguration = systemObjectCache.getSystemConfiguration(result); + if (systemConfiguration == null) { + // This happens in some tests. And also during first startup. + return; + } + context.setSystemConfiguration(systemConfiguration); + SystemConfigurationType systemConfigurationType = systemConfiguration.asObjectable(); + + if (context.getFocusContext() != null) { + if (context.getFocusContext().getArchetypePolicyType() == null) { + ArchetypePolicyType archetypePolicy = determineArchetypePolicy(context, task, result); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Selected archetype policy:\n{}", + archetypePolicy==null?null:archetypePolicy.asPrismContainerValue().debugDump(1)); + } + context.getFocusContext().setArchetypePolicyType(archetypePolicy); + } + } + + if (context.getFocusTemplate() == null) { + // TODO is the nullity check needed here? + setFocusTemplate(context, result); + } + + if (context.getAccountSynchronizationSettings() == null) { + ProjectionPolicyType globalAccountSynchronizationSettings = systemConfigurationType.getGlobalAccountSynchronizationSettings(); + LOGGER.trace("Applying globalAccountSynchronizationSettings to context: {}", globalAccountSynchronizationSettings); + context.setAccountSynchronizationSettings(globalAccountSynchronizationSettings); + } + + loadSecurityPolicy(context, task, result); + } + + private ArchetypePolicyType determineArchetypePolicy(LensContext context, Task task, OperationResult result) throws SchemaException, ConfigurationException { + PrismObject systemConfiguration = context.getSystemConfiguration(); + if (systemConfiguration == null) { + return null; + } + if (context.getFocusContext() == null) { + return null; + } + PrismObject object = context.getFocusContext().getObjectAny(); + String explicitArchetypeOid = LensUtil.determineExplicitArchetypeOid(context.getFocusContext().getObjectAny()); + return archetypeManager.determineArchetypePolicy(object, explicitArchetypeOid, result); + } + + public ArchetypeType updateArchetype(LensContext context, Task task, OperationResult result) throws SchemaException, ConfigurationException { + PrismObject systemConfiguration = context.getSystemConfiguration(); + if (systemConfiguration == null) { + return null; + } + if (context.getFocusContext() == null) { + return null; + } + + PrismObject object = context.getFocusContext().getObjectAny(); + + String explicitArchetypeOid = LensUtil.determineExplicitArchetypeOid(context.getFocusContext().getObjectAny()); + PrismObject archetype = archetypeManager.determineArchetype(object, explicitArchetypeOid, result); + ArchetypeType archetypeType = null; + if (archetype != null) { + archetypeType = archetype.asObjectable(); + } + + context.getFocusContext().setArchetype(archetypeType); + + return archetypeType; + } + + public void updateArchetypePolicy(LensContext context, Task task, OperationResult result) throws SchemaException, ConfigurationException { + if (context.getFocusContext() == null) { + return; + } + ArchetypePolicyType newArchetypePolicy = determineArchetypePolicy(context, task, result); + if (newArchetypePolicy != context.getFocusContext().getArchetypePolicyType()) { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Changed policy configuration because of changed subtypes:\n{}", + newArchetypePolicy==null?null:newArchetypePolicy.asPrismContainerValue().debugDump(1)); + } + context.getFocusContext().setArchetypePolicyType(newArchetypePolicy); + } + } + + // expects that object policy configuration is already set in focusContext + public void setFocusTemplate(LensContext context, OperationResult result) + throws ObjectNotFoundException, SchemaException { + + // 1. When this method is called after inbound processing, we might want to change the existing template + // (because e.g. subtype or archetype was determined and we want to move from generic to more specific template). + // 2. On the other hand, if focus template is set up explicitly from the outside (e.g. in synchronization section) + // we probably do not want to change it here. + if (context.getFocusTemplate() != null && context.isFocusTemplateExternallySet()) { + return; + } + + String currentOid = context.getFocusTemplate() != null ? context.getFocusTemplate().getOid() : null; + String newOid; + + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext == null) { + newOid = null; + } else { + ArchetypePolicyType archetypePolicy = focusContext.getArchetypePolicyType(); + if (archetypePolicy == null) { + LOGGER.trace("No default object template (no policy)"); + newOid = null; + } else { + ObjectReferenceType templateRef = archetypePolicy.getObjectTemplateRef(); + if (templateRef == null) { + LOGGER.trace("No default object template (no templateRef)"); + newOid = null; + } else { + newOid = templateRef.getOid(); + } + } + } + + LOGGER.trace("current focus template OID = {}, new = {}", currentOid, newOid); + if (!java.util.Objects.equals(currentOid, newOid)) { + ObjectTemplateType template; + if (newOid != null) { + template = cacheRepositoryService.getObject(ObjectTemplateType.class, newOid, null, result).asObjectable(); + } else { + template = null; + } + context.setFocusTemplate(template); + } + } + + private void loadLinkRefs(LensContext context, Task task, OperationResult result) throws ObjectNotFoundException, + SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext == null) { + // Nothing to load + return; + } + + LOGGER.trace("loadLinkRefs starting"); + + PrismObject focusCurrent = focusContext.getObjectCurrent(); + if (focusCurrent != null) { + loadLinkRefsFromFocus(context, focusCurrent, task, result); + LOGGER.trace("loadLinkRefsFromFocus done"); + } + + if (consistencyChecks) context.checkConsistence(); + + loadLinkRefsFromDelta(context, focusCurrent, focusContext, task, result); + LOGGER.trace("loadLinkRefsFromDelta done"); + + if (consistencyChecks) context.checkConsistence(); + + loadProjectionContextsSync(context, task, result); + LOGGER.trace("loadProjectionContextsSync done"); + + if (consistencyChecks) context.checkConsistence(); + } + + /** + * Does not overwrite existing account contexts, just adds new ones. + */ + private void loadLinkRefsFromFocus(LensContext context, PrismObject focus, + Task task, OperationResult result) throws ObjectNotFoundException, + CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { + PrismReference linkRef = focus.findReference(FocusType.F_LINK_REF); + if (linkRef == null) { + return; + } + for (PrismReferenceValue linkRefVal : linkRef.getValues()) { + String oid = linkRefVal.getOid(); + if (StringUtils.isBlank(oid)) { + LOGGER.trace("Null or empty OID in link reference {} in:\n{}", linkRef, + focus.debugDump(1)); + throw new SchemaException("Null or empty OID in link reference in " + focus); + } + LensProjectionContext existingAccountContext = findAccountContext(oid, context); + +// if (!canBeLoaded(context, existingAccountContext)) { +// continue; +// } + + if (existingAccountContext != null) { + // TODO: do we need to reload the account inside here? yes we need + + existingAccountContext.setFresh(true); + continue; + } + PrismObject shadow; + //noinspection unchecked + PrismObject shadowFromLink = linkRefVal.getObject(); + if (shadowFromLink == null) { + GetOperationOptions rootOpts; + if (context.isDoReconciliationForAllProjections()) { + rootOpts = GetOperationOptions.createForceRetry(); + } else { + // Using NO_FETCH so we avoid reading in a full account. This is more efficient as we don't need full account here. + // We need to fetch from provisioning and not repository so the correct definition will be set. + rootOpts = GetOperationOptions.createNoFetch(); + rootOpts.setPointInTimeType(PointInTimeType.FUTURE); + } + + Collection> options = SelectorOptions.createCollection(rootOpts); + LOGGER.trace("Loading shadow {} from linkRef, options={}", oid, options); + try { + shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); + } catch (ObjectNotFoundException e) { + // Broken accountRef. We need to mark it for deletion + LensProjectionContext projectionContext = getOrCreateEmptyThombstoneProjectionContext(context, oid); + projectionContext.setFresh(true); + projectionContext.setExists(false); + projectionContext.setShadowExistsInRepo(false); + OperationResult getObjectSubresult = result.getLastSubresult(); + getObjectSubresult.setErrorsHandled(); + continue; + } + } else { + shadow = shadowFromLink; + // Make sure it has a proper definition. This may come from outside of the model. + provisioningService.applyDefinition(shadow, task, result); + } + LensProjectionContext projectionContext = getOrCreateAccountContext(context, shadow, task, result); + projectionContext.setFresh(true); + projectionContext.setExists(ShadowUtil.isExists(shadow.asObjectable())); + if (ShadowUtil.isDead(shadow.asObjectable())) { + projectionContext.markTombstone(); + LOGGER.trace("Loading dead shadow {} for projection {}.", shadow, projectionContext.getHumanReadableName()); + continue; + } + if (context.isDoReconciliationForAllProjections()) { + projectionContext.setDoReconciliation(true); + } + if (projectionContext.isDoReconciliation()) { + // Do not load old account now. It will get loaded later in the + // reconciliation step. + continue; + } + projectionContext.setLoadedObject(shadow); + } + } + + private void loadLinkRefsFromDelta(LensContext context, PrismObject focus, + LensFocusContext focusContext, Task task, OperationResult result) throws SchemaException, + ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { + + ObjectDelta focusPrimaryDelta = focusContext.getPrimaryDelta(); + if (focusPrimaryDelta == null) { + return; + } + + ReferenceDelta linkRefDelta; + if (focusPrimaryDelta.getChangeType() == ChangeType.ADD) { + PrismReference linkRef = focusPrimaryDelta.getObjectToAdd().findReference( + FocusType.F_LINK_REF); + if (linkRef == null) { + // Adding new focus with no linkRef -> nothing to do + return; + } + linkRefDelta = linkRef.createDelta(FocusType.F_LINK_REF); + linkRefDelta.addValuesToAdd(PrismValueCollectionsUtil.cloneValues(linkRef.getValues())); + } else if (focusPrimaryDelta.getChangeType() == ChangeType.MODIFY) { + linkRefDelta = focusPrimaryDelta.findReferenceModification(FocusType.F_LINK_REF); + if (linkRefDelta == null) { + return; + } + } else { + // delete, all existing account are already marked for delete + return; + } + + if (linkRefDelta.isReplace()) { + // process "replace" by distributing values to delete and add + linkRefDelta = (ReferenceDelta) linkRefDelta.clone(); + PrismReference linkRef = focus.findReference(FocusType.F_LINK_REF); + linkRefDelta.distributeReplace(linkRef == null ? null : linkRef.getValues()); + } + + if (linkRefDelta.getValuesToAdd() != null) { + for (PrismReferenceValue refVal : linkRefDelta.getValuesToAdd()) { + String oid = refVal.getOid(); + LensProjectionContext projectionContext = null; + PrismObject shadow = null; + boolean isCombinedAdd = false; + if (oid == null) { + // Adding new account + shadow = refVal.getObject(); + if (shadow == null) { + throw new SchemaException("Null or empty OID in account reference " + refVal + " in " + + focus); + } + provisioningService.applyDefinition(shadow, task, result); + if (consistencyChecks) ShadowUtil.checkConsistence(shadow, "account from "+linkRefDelta); + // Check for conflicting change + projectionContext = LensUtil.getProjectionContext(context, shadow, provisioningService, prismContext, task, result); + if (projectionContext != null) { + // There is already existing context for the same discriminator. Tolerate this only if + // the deltas match. It is an error otherwise. + ObjectDelta primaryDelta = projectionContext.getPrimaryDelta(); + if (primaryDelta == null) { + throw new SchemaException("Attempt to add "+shadow+" to a focus that already contains "+ + projectionContext.getHumanReadableKind()+" of type '"+ + projectionContext.getResourceShadowDiscriminator().getIntent()+"' on "+projectionContext.getResource()); + } + if (!primaryDelta.isAdd()) { + throw new SchemaException("Conflicting changes in the context. " + + "Add of linkRef in the focus delta with embedded object conflicts with explicit delta "+primaryDelta); + } + if (!shadow.equals(primaryDelta.getObjectToAdd())) { + throw new SchemaException("Conflicting changes in the context. " + + "Add of linkRef in the focus delta with embedded object is not adding the same object as explicit delta "+primaryDelta); + } + } else { + // Create account context from embedded object + projectionContext = createProjectionContext(context, shadow, task, result); + } + // This is a new account that is to be added. So it should + // go to account primary delta + ObjectDelta accountPrimaryDelta = shadow.createAddDelta(); + projectionContext.setPrimaryDelta(accountPrimaryDelta); + projectionContext.setFullShadow(true); + projectionContext.setExists(false); + isCombinedAdd = true; + } else { + // We have OID. This is either linking of existing account or + // add of new account + // therefore check for account existence to decide + try { + // Using NO_FETCH so we avoid reading in a full account. This is more efficient as we don't need full account here. + // We need to fetch from provisioning and not repository so the correct definition will be set. + GetOperationOptions rootOpts = GetOperationOptions.createNoFetch(); + rootOpts.setPointInTimeType(PointInTimeType.FUTURE); + Collection> options = SelectorOptions.createCollection(rootOpts); + shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); + // Create account context from retrieved object + projectionContext = getOrCreateAccountContext(context, shadow, task, result); + projectionContext.setLoadedObject(shadow); + projectionContext.setExists(ShadowUtil.isExists(shadow.asObjectable())); + } catch (ObjectNotFoundException e) { + if (refVal.getObject() == null) { + // account does not exist, no composite account in + // ref -> this is really an error + throw e; + } else { + // New account (with OID) + result.muteLastSubresultError(); + shadow = refVal.getObject(); + if (!shadow.hasCompleteDefinition()) { + provisioningService.applyDefinition(shadow, task, result); + } + // Create account context from embedded object + projectionContext = createProjectionContext(context, shadow, task, result); + ObjectDelta accountPrimaryDelta = shadow.createAddDelta(); + projectionContext.setPrimaryDelta(accountPrimaryDelta); + projectionContext.setFullShadow(true); + projectionContext.setExists(false); + projectionContext.setShadowExistsInRepo(false); + isCombinedAdd = true; + } + } + } + if (context.isDoReconciliationForAllProjections() && !isCombinedAdd) { + projectionContext.setDoReconciliation(true); + } + projectionContext.setFresh(true); + } + } + + if (linkRefDelta.getValuesToDelete() != null) { + for (PrismReferenceValue refVal : linkRefDelta.getValuesToDelete()) { + String oid = refVal.getOid(); + LensProjectionContext projectionContext = null; + PrismObject shadow = null; + if (oid == null) { + throw new SchemaException("Cannot delete account ref without an oid in " + focus); + } else { + try { + // Using NO_FETCH so we avoid reading in a full account. This is more efficient as we don't need full account here. + // We need to fetch from provisioning and not repository so the correct definition will be set. + Collection> options = SelectorOptions.createCollection(GetOperationOptions.createNoFetch()); + shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); + // Create account context from retrieved object + projectionContext = getOrCreateAccountContext(context, shadow, task, result); + projectionContext.setLoadedObject(shadow); + projectionContext.setExists(ShadowUtil.isExists(shadow.asObjectable())); + } catch (ObjectNotFoundException e) { + try{ + // Broken accountRef. We need to try again with raw options, because the error should be thrown because of non-existent resource + Collection> options = SelectorOptions.createCollection(GetOperationOptions.createRaw()); + shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); + projectionContext = getOrCreateEmptyThombstoneProjectionContext(context, oid); + projectionContext.setFresh(true); + projectionContext.setExists(false); + projectionContext.setShadowExistsInRepo(false); + OperationResult getObjectSubresult = result.getLastSubresult(); + getObjectSubresult.setErrorsHandled(); + } catch (ObjectNotFoundException ex){ + // This is still OK. It means deleting an accountRef + // that points to non-existing object + // just log a warning + LOGGER.warn("Deleting accountRef of " + focus + " that points to non-existing OID " + + oid); + } + + } + } + if (projectionContext != null) { + if (refVal.getObject() == null) { + projectionContext.setSynchronizationIntent(SynchronizationIntent.UNLINK); + } else { + projectionContext.setSynchronizationIntent(SynchronizationIntent.DELETE); + ObjectDelta accountPrimaryDelta = shadow.createDeleteDelta(); + projectionContext.setPrimaryDelta(accountPrimaryDelta); + } + projectionContext.setFresh(true); + } + + } + } + + // remove the accountRefs without oid. These will get into the way now. + // The accounts + // are in the context now and will be linked at the end of the process + // (it they survive the policy) + // We need to make sure this happens on the real primary focus delta + + ObjectDelta primaryDeltaToUpdate; + if (focusPrimaryDelta.isImmutable()) { + primaryDeltaToUpdate = focusPrimaryDelta.clone(); + focusContext.setPrimaryDelta(primaryDeltaToUpdate); + } else { + primaryDeltaToUpdate = focusPrimaryDelta; + } + + if (primaryDeltaToUpdate.getChangeType() == ChangeType.ADD) { + primaryDeltaToUpdate.getObjectToAdd().removeReference(FocusType.F_LINK_REF); + } else if (primaryDeltaToUpdate.getChangeType() == ChangeType.MODIFY) { + primaryDeltaToUpdate.removeReferenceModification(FocusType.F_LINK_REF); + } + // It is little bit questionable whether we need to make primary delta immutable. It makes some sense, but I am not sure. + // Note that (as a side effect) this can make "focus new" immutable as well, in the case of ADD delta. + primaryDeltaToUpdate.freeze(); + } + + private void loadProjectionContextsSync(LensContext context, Task task, OperationResult result) throws SchemaException, + ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + for (LensProjectionContext projCtx : context.getProjectionContexts()) { + if (projCtx.isFresh() && projCtx.getObjectCurrent() != null) { + // already loaded + continue; + } + ObjectDelta syncDelta = projCtx.getSyncDelta(); + if (syncDelta != null) { + if (projCtx.isDoReconciliation()) { + // Do not load old account now. It will get loaded later in the + // reconciliation step. Just mark it as fresh. + projCtx.setFresh(true); + continue; + } + String oid = syncDelta.getOid(); + PrismObject shadow = null; + + if (syncDelta.getChangeType() == ChangeType.ADD) { + shadow = syncDelta.getObjectToAdd().clone(); + projCtx.setLoadedObject(shadow); + projCtx.setExists(ShadowUtil.isExists(shadow.asObjectable())); + + } else { + + if (oid == null) { + throw new IllegalArgumentException("No OID in sync delta in " + projCtx); + } + // Using NO_FETCH so we avoid reading in a full account. This is more efficient as we don't need full account here. + // We need to fetch from provisioning and not repository so the correct definition will be set. + GetOperationOptions option = GetOperationOptions.createNoFetch(); + option.setDoNotDiscovery(true); + option.setPointInTimeType(PointInTimeType.FUTURE); + Collection> options = SelectorOptions.createCollection(option); + + try { + + shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); + + } catch (ObjectNotFoundException e) { + LOGGER.trace("Loading shadow {} from sync delta failed: not found", oid); + projCtx.setExists(false); + projCtx.setObjectCurrent(null); + projCtx.setShadowExistsInRepo(false); + } + + // We will not set old account if the delta is delete. The + // account does not really exists now. + // (but the OID and resource will be set from the repo + // shadow) + if (syncDelta.getChangeType() == ChangeType.DELETE) { + projCtx.markTombstone(); + } else if (shadow != null) { + syncDelta.applyTo(shadow); + projCtx.setLoadedObject(shadow); + projCtx.setExists(ShadowUtil.isExists(shadow.asObjectable())); + } + } + + // Make sure OID is set correctly + projCtx.setOid(oid); + // Make sure that resource is also resolved + if (projCtx.getResource() == null && shadow != null) { + String resourceOid = ShadowUtil.getResourceOid(shadow.asObjectable()); + if (resourceOid == null) { + throw new IllegalArgumentException("No resource OID in " + shadow); + } + ResourceType resourceType = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result); + projCtx.setResource(resourceType); + } + projCtx.setFresh(true); + } + } + } + +// private boolean canBeLoaded(LensContext context, LensProjectionContext projCtx){ +// if (QNameUtil.qNameToUri(SchemaConstants.CHANGE_CHANNEL_DISCOVERY).equals(context.getChannel()) && projCtx == null && ModelExecuteOptions.isLimitPropagation(context.getOptions())) { +// // avoid to create projection context if the channel which +// // triggered this operation is discovery..we need only +// // projection context of discovered shadow +// return false; +// } +// return true; +// } + + private LensProjectionContext getOrCreateAccountContext(LensContext context, + PrismObject projection, Task task, OperationResult result) throws ObjectNotFoundException, + CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { + ShadowType shadowType = projection.asObjectable(); + String resourceOid = ShadowUtil.getResourceOid(shadowType); + if (resourceOid == null) { + throw new SchemaException("The " + projection + " has null resource reference OID"); + } + + LensProjectionContext projectionContext = context.findProjectionContextByOid(shadowType.getOid()); + + if (projectionContext == null) { + String intent = ShadowUtil.getIntent(shadowType); + ShadowKindType kind = ShadowUtil.getKind(shadowType); + ResourceType resource = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result); + intent = LensUtil.refineProjectionIntent(kind, intent, resource, prismContext); + boolean thombstone = false; + if (ShadowUtil.isDead(shadowType)) { + thombstone = true; + } + ResourceShadowDiscriminator rsd = new ResourceShadowDiscriminator(resourceOid, kind, intent, shadowType.getTag(), thombstone); + projectionContext = LensUtil.getOrCreateProjectionContext(context, rsd); + + if (projectionContext.getOid() == null) { + projectionContext.setOid(projection.getOid()); + } else if (projection.getOid() != null && !projectionContext.getOid().equals(projection.getOid())) { + // Conflict. We have existing projection and another project that is added (with the same discriminator). + // Chances are that the old object is already deleted (e.g. during rename). So let's be + // slightly inefficient here and check for existing shadow existence + try { + GetOperationOptions rootOpt = GetOperationOptions.createPointInTimeType(PointInTimeType.FUTURE); + rootOpt.setDoNotDiscovery(true); + Collection> opts = SelectorOptions.createCollection(rootOpt); + LOGGER.trace("Projection conflict detected, existing: {}, new {}", projectionContext.getOid(), projection.getOid()); + PrismObject existingShadow = provisioningService.getObject(ShadowType.class, projectionContext.getOid(), opts, task, result); + // Maybe it is the other way around + try { + PrismObject newShadow = provisioningService.getObject(ShadowType.class, projection.getOid(), opts, task, result); + // Obviously, two projections with the same discriminator exists + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Projection {} already exists in context\nExisting:\n{}\nNew:\n{}", rsd, + existingShadow.debugDump(1), newShadow.debugDump(1)); + } + if (!ShadowUtil.isDead(newShadow.asObjectable())) { + throw new PolicyViolationException("Projection "+rsd+" already exists in context (existing "+existingShadow+", new "+projection); + } + // Dead shadow. This is somehow expected, fix it and we can go on + rsd.setTombstone(true); + projectionContext = LensUtil.getOrCreateProjectionContext(context, rsd); + projectionContext.setExists(ShadowUtil.isExists(newShadow.asObjectable())); + projectionContext.setFullShadow(false); + } catch (ObjectNotFoundException e) { + // This is somehow expected, fix it and we can go on + result.muteLastSubresultError(); + // We have to create new context in this case, but it has to have thumbstone set + rsd.setTombstone(true); + projectionContext = LensUtil.getOrCreateProjectionContext(context, rsd); + // We have to mark it as dead right now, otherwise the uniqueness check may fail + markShadowDead(projection.getOid(), result); + projectionContext.setShadowExistsInRepo(false); + } + } catch (ObjectNotFoundException e) { + // This is somehow expected, fix it and we can go on + result.muteLastSubresultError(); + String shadowOid = projectionContext.getOid(); + projectionContext.getResourceShadowDiscriminator().setTombstone(true); + projectionContext = LensUtil.getOrCreateProjectionContext(context, rsd); + projectionContext.setShadowExistsInRepo(false); + // We have to mark it as dead right now, otherwise the uniqueness check may fail + markShadowDead(shadowOid, result); + } + } + } + return projectionContext; + } + + private void markShadowDead(String oid, OperationResult result) { + if (oid == null) { + // nothing to mark + return; + } + Collection> modifications = MiscSchemaUtil.createCollection( + prismContext.deltaFactory().property().createReplaceDelta(prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(ShadowType.class), + ShadowType.F_DEAD, true)); + try { + cacheRepositoryService.modifyObject(ShadowType.class, oid, modifications, result); + // TODO report to task? + } catch (ObjectNotFoundException e) { + // Done already + result.muteLastSubresultError(); + } catch (ObjectAlreadyExistsException | SchemaException e) { + // Should not happen + throw new SystemException(e.getMessage(), e); + } + } + + + private LensProjectionContext createProjectionContext(LensContext context, + PrismObject account, Task task, OperationResult result) throws ObjectNotFoundException, + CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + ShadowType shadowType = account.asObjectable(); + String resourceOid = ShadowUtil.getResourceOid(shadowType); + if (resourceOid == null) { + throw new SchemaException("The " + account + " has null resource reference OID"); + } + String intent = ShadowUtil.getIntent(shadowType); + ShadowKindType kind = ShadowUtil.getKind(shadowType); + ResourceType resource = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result); + String accountIntent = LensUtil.refineProjectionIntent(kind, intent, resource, prismContext); + ResourceShadowDiscriminator rsd = new ResourceShadowDiscriminator(resourceOid, kind, accountIntent, shadowType.getTag(), false); + LensProjectionContext accountSyncContext = context.findProjectionContext(rsd); + if (accountSyncContext != null) { + throw new SchemaException("Attempt to add "+account+" to a focus that already contains projection of type '"+accountIntent+"' on "+resource); + } + accountSyncContext = context.createProjectionContext(rsd); + accountSyncContext.setResource(resource); + accountSyncContext.setOid(account.getOid()); + return accountSyncContext; + } + + private LensProjectionContext findAccountContext(String accountOid, LensContext context) { + for (LensProjectionContext accContext : context.getProjectionContexts()) { + if (accountOid.equals(accContext.getOid())) { + return accContext; + } + } + + return null; + } + + private LensProjectionContext getOrCreateEmptyThombstoneProjectionContext(LensContext context, + String missingShadowOid) { + LensProjectionContext projContext = context.findProjectionContextByOid(missingShadowOid); + if (projContext == null) { + projContext = context.createProjectionContext(null); + projContext.setOid(missingShadowOid); + } + + if (projContext.getResourceShadowDiscriminator() == null) { + projContext.setResourceShadowDiscriminator(new ResourceShadowDiscriminator(null, null, null, null, true)); + } else { + projContext.markTombstone(); + } + + projContext.setFullShadow(false); + projContext.setObjectCurrent(null); + + return projContext; + } + + /** + * Check reconcile flag in account sync context and set accountOld + * variable if it's not set (from provisioning), load resource (if not set already), etc. + */ + private void finishLoadOfProjectionContext(LensContext context, + LensProjectionContext projContext, Task task, OperationResult result) + throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, + SecurityViolationException, ExpressionEvaluationException { + + if (projContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { + return; + } + + // MID-2436 (volatile objects) - as a quick but effective hack, we set reconciliation:=TRUE for volatile accounts + ResourceObjectTypeDefinitionType objectDefinition = projContext.getResourceObjectTypeDefinitionType(); + if (objectDefinition != null && objectDefinition.getVolatility() == ResourceObjectVolatilityType.UNPREDICTABLE && !projContext.isDoReconciliation()) { + LOGGER.trace("Resource object volatility is UNPREDICTABLE => setting doReconciliation to TRUE for {}", projContext.getResourceShadowDiscriminator()); + projContext.setDoReconciliation(true); + } + + // Remember OID before the object could be wiped + String projectionObjectOid = projContext.getOid(); + if (projContext.isDoReconciliation() && !projContext.isFullShadow()) { + // The current object is useless here. So lets just wipe it so it will get loaded + projContext.setObjectCurrent(null); + } + + // Load current object + boolean tombstone = false; + PrismObject projectionObject = projContext.getObjectCurrent(); + if (projContext.getObjectCurrent() == null || needToReload(context, projContext)) { + if (projContext.isAdd()) { + // No need to load old object, there is none + projContext.setExists(false); + projContext.recompute(); + projectionObject = projContext.getObjectNew(); + } else { + if (projectionObjectOid == null) { + projContext.setExists(false); + if (projContext.getResourceShadowDiscriminator() == null || projContext.getResourceShadowDiscriminator().getResourceOid() == null) { + throw new SystemException( + "Projection "+projContext.getHumanReadableName()+" with null OID, no representation and no resource OID in account sync context "+projContext); + } + } else { + GetOperationOptions rootOptions = GetOperationOptions.createPointInTimeType(PointInTimeType.FUTURE); + if (projContext.isDoReconciliation()) { + rootOptions.setForceRefresh(true); + if (SchemaConstants.CHANGE_CHANNEL_DISCOVERY_URI.equals(context.getChannel())) { + // Avoid discovery loops + rootOptions.setDoNotDiscovery(true); + } + } else { + rootOptions.setNoFetch(true); + } + rootOptions.setAllowNotFound(true); + Collection> options = SelectorOptions.createCollection(rootOptions); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Loading shadow {} for projection {}, options={}", projectionObjectOid, projContext.getHumanReadableName(), options); + } + + try { + PrismObject objectOld = provisioningService.getObject( + projContext.getObjectTypeClass(), projectionObjectOid, options, task, result); + if (LOGGER.isTraceEnabled()) { + if (!GetOperationOptions.isNoFetch(rootOptions) && !GetOperationOptions.isRaw(rootOptions)) { + LOGGER.trace("Full shadow loaded for {}:\n{}", projContext.getHumanReadableName(), objectOld.debugDump(1)); + } + } + Validate.notNull(objectOld.getOid()); + if (InternalsConfig.consistencyChecks) { + String resourceOid = projContext.getResourceOid(); + if (resourceOid != null && !resourceOid.equals(objectOld.asObjectable().getResourceRef().getOid())) { + throw new IllegalStateException("Loaded shadow with wrong resourceRef. Loading shadow "+projectionObjectOid+", got "+ + objectOld.getOid()+", expected resourceRef "+resourceOid+", but was "+objectOld.asObjectable().getResourceRef().getOid()+ + " for context "+projContext.getHumanReadableName()); + } + } + projContext.setLoadedObject(objectOld); + if (projContext.isDoReconciliation()) { + projContext.determineFullShadowFlag(objectOld); + } else { + projContext.setFullShadow(false); + } + projectionObject = objectOld; + if (ShadowUtil.isExists(objectOld.asObjectable())) { + projContext.setExists(true); + } else { + projContext.setExists(false); + if (ShadowUtil.isDead(objectOld.asObjectable())) { + projContext.markTombstone(); + } + LOGGER.debug("Foud only dead {} for projection context {}.", objectOld, projContext.getHumanReadableName()); + tombstone = true; + } + + } catch (ObjectNotFoundException ex) { + LOGGER.debug("Could not find object with oid {} for projection context {}.", projectionObjectOid, projContext.getHumanReadableName()); + // This does not mean BROKEN. The projection was there, but it gone now. + // Consistency mechanism might have kicked in and fixed the shadow. + // What we really want here is a thombstone projection or a refreshed projection. + result.muteLastSubresultError(); + projContext.setShadowExistsInRepo(false); + refreshContextAfterShadowNotFound(context, projContext, options, task, result); + + } catch (CommunicationException | SchemaException | ConfigurationException | SecurityViolationException + | RuntimeException | Error e) { + + LOGGER.warn("Problem while getting object with oid {}. Projection context {} is marked as broken: {}: {}", + projectionObjectOid, projContext.getHumanReadableName(), e.getClass().getSimpleName(), e.getMessage()); + projContext.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.BROKEN); + + ResourceType resourceType = projContext.getResource(); + if (resourceType == null) { + throw e; + } else { + ErrorSelectorType errorSelector = null; + if (resourceType.getConsistency() != null) { + errorSelector = resourceType.getConsistency().getConnectorErrorCriticality(); + } + if (errorSelector == null) { + if (e instanceof SchemaException) { + // Just continue evaluation. The error is recorded in the result. + // The consistency mechanism has (most likely) already done the best. + // We cannot do any better. + return; + } else { + throw e; + } + } else { + if (CriticalityType.FATAL.equals(ExceptionUtil.getCriticality(errorSelector, e, CriticalityType.FATAL))) { + throw e; + } else { + return; + } + } + } + } + + } + projContext.setFresh(true); + } + } else { + projectionObject = projContext.getObjectCurrent(); + if (projectionObjectOid != null) { + projContext.setExists(ShadowUtil.isExists(projectionObject.asObjectable())); + } + } + + + // Determine Resource + ResourceType resourceType = projContext.getResource(); + String resourceOid = null; + if (resourceType == null) { + if (projectionObject != null) { + ShadowType shadowType = projectionObject.asObjectable(); + resourceOid = ShadowUtil.getResourceOid(shadowType); + } else if (projContext.getResourceShadowDiscriminator() != null) { + resourceOid = projContext.getResourceShadowDiscriminator().getResourceOid(); + } else if (!tombstone) { + throw new IllegalStateException("No shadow, no discriminator and not tombstone? That won't do. Projection "+projContext.getHumanReadableName()); + } + } else { + resourceOid = resourceType.getOid(); + } + + // Determine discriminator + ResourceShadowDiscriminator discr = projContext.getResourceShadowDiscriminator(); + if (discr == null) { + if (projectionObject != null) { + ShadowType accountShadowType = projectionObject.asObjectable(); + String intent = ShadowUtil.getIntent(accountShadowType); + ShadowKindType kind = ShadowUtil.getKind(accountShadowType); + discr = new ResourceShadowDiscriminator(resourceOid, kind, intent, accountShadowType.getTag(), tombstone); + } else { + discr = new ResourceShadowDiscriminator(null, null, null, null, tombstone); + } + projContext.setResourceShadowDiscriminator(discr); + } else { + if (tombstone) { + // We do not want to reset tombstone flag if it was set before + projContext.markTombstone(); + } + } + + // Load resource + if (resourceType == null && resourceOid != null) { + resourceType = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result); + projContext.setResource(resourceType); + } + + //Determine refined schema and password policies for account type + RefinedObjectClassDefinition structuralObjectClassDef = projContext.getStructuralObjectClassDefinition(); + if (structuralObjectClassDef != null) { + LOGGER.trace("Finishing loading of projection context: security policy"); + SecurityPolicyType projectionSecurityPolicy = securityHelper.locateProjectionSecurityPolicy(projContext.getStructuralObjectClassDefinition(), task, result); + LOGGER.trace("Located security policy for: {},\n {}", projContext, projectionSecurityPolicy); + projContext.setProjectionSecurityPolicy(projectionSecurityPolicy); + } else { + LOGGER.trace("No structural object class definition, skipping determining security policy"); + } + + //set limitation, e.g. if this projection context should be recomputed and processed by projector + if (ModelExecuteOptions.isLimitPropagation(context.getOptions())){ + if (context.getTriggeredResourceOid() != null){ + if (!context.getTriggeredResourceOid().equals(resourceOid)){ + projContext.setCanProject(false); + } + } + } + + setPrimaryDeltaOldValue(projContext); + } + + private boolean needToReload(LensContext context, + LensProjectionContext projContext) { + ResourceShadowDiscriminator discr = projContext.getResourceShadowDiscriminator(); + if (discr == null) { + return false; + } + // This is kind of brutal. But effective. We are reloading all higher-order dependencies + // before they are processed. This makes sure we have fresh state when they are re-computed. + // Because higher-order dependencies may have more than one projection context and the + // changes applied to one of them are not automatically reflected on on other. therefore we need to reload. + if (discr.getOrder() == 0) { + return false; + } + int executionWave = context.getExecutionWave(); + int projCtxWave = projContext.getWave(); + if (executionWave == projCtxWave - 1) { + // Reload right before its execution wave + return true; + } + return false; + } + + private void fullCheckConsistence(LensContext context) { + context.checkConsistence(); + for (LensProjectionContext projectionContext: context.getProjectionContexts()) { + if (projectionContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) { + continue; + } + if (projectionContext.getResourceShadowDiscriminator() == null) { + throw new IllegalStateException("No discriminator in "+projectionContext); + } + } + } + + public void loadFullShadow(LensContext context, LensProjectionContext projCtx, String reason, Task task, OperationResult parentResult) + throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + if (projCtx.isFullShadow()) { + // already loaded + return; + } + if (projCtx.isAdd() && projCtx.getOid() == null) { + // nothing to load yet + return; + } + if (projCtx.isTombstone()) { + // loading is futile + return; + } + OperationResult result = parentResult.subresult(CLASS_DOT + "loadFullShadow") + .setMinor() + .build(); + FullShadowLoadedTraceType trace; + if (result.isTraced()) { + trace = new FullShadowLoadedTraceType(prismContext); + if (result.isTracingNormal(FullShadowLoadedTraceType.class)) { + trace.setInputLensContextText(context.debugDump()); + ResourceType resource = projCtx.getResource(); + PolyStringType name = resource != null ? resource.getName() : null; + trace.setResourceName(name != null ? name : PolyStringType.fromOrig(projCtx.getResourceOid())); + } + trace.setInputLensContext(context.toLensContextType(getExportType(trace, result))); + result.addTrace(trace); + } else { + trace = null; + } + try { + ResourceShadowDiscriminator discr = projCtx.getResourceShadowDiscriminator(); + if (discr != null && discr.getOrder() > 0) { + // It may be just too early to load the projection + if (LensUtil.hasLowerOrderContext(context, projCtx) && (context.getExecutionWave() < projCtx.getWave())) { + // We cannot reliably load the context now + result.addReturn(DEFAULT, "too early"); + return; + } + } + + GetOperationOptions getOptions = GetOperationOptions.createAllowNotFound(); + getOptions.setPointInTimeType(PointInTimeType.FUTURE); + if (projCtx.isDoReconciliation()) { + getOptions.setForceRefresh(true); + } + if (SchemaConstants.CHANGE_CHANNEL_DISCOVERY_URI.equals(context.getChannel())) { + LOGGER.trace("Loading full resource object {} from provisioning - with doNotDiscover to avoid loops; reason: {}", + projCtx, reason); + // Avoid discovery loops + getOptions.setDoNotDiscovery(true); + } else { + LOGGER.trace("Loading full resource object {} from provisioning (discovery enabled), reason: {}, channel: {}", + projCtx, reason, context.getChannel()); + } + Collection> options = SelectorOptions.createCollection(getOptions); + applyAttributesToGet(projCtx, options); + try { + PrismObject objectCurrent = provisioningService + .getObject(ShadowType.class, projCtx.getOid(), options, task, result); + Validate.notNull(objectCurrent.getOid()); + if (trace != null) { + trace.setShadowLoadedRef(ObjectTypeUtil.createObjectRefWithFullObject(objectCurrent, prismContext)); + } + // TODO: use setLoadedObject() instead? + projCtx.setObjectCurrent(objectCurrent); + projCtx.determineFullShadowFlag(objectCurrent); + if (ShadowUtil.isExists(objectCurrent.asObjectable())) { + result.addReturn(DEFAULT, "found"); + } else { + LOGGER.debug("Load of full resource object {} ended with non-existent shadow (options={})", projCtx, + getOptions); + projCtx.setExists(false); + refreshContextAfterShadowNotFound(context, projCtx, options, task, result); + result.addReturn(DEFAULT, "not found"); + } + + } catch (ObjectNotFoundException ex) { + LOGGER.debug("Load of full resource object {} ended with ObjectNotFoundException (options={})", projCtx, + getOptions); + result.muteLastSubresultError(); + projCtx.setShadowExistsInRepo(false); + refreshContextAfterShadowNotFound(context, projCtx, options, task, result); + result.addReturn(DEFAULT, "not found"); + } + + projCtx.recompute(); + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Loaded full resource object:\n{}", projCtx.debugDump(1)); + } + } catch (Throwable t) { + result.recordFatalError(t); + throw t; + } finally { + if (trace != null) { + if (result.isTracingNormal(FullShadowLoadedTraceType.class)) { + trace.setOutputLensContextText(context.debugDump()); + } + trace.setOutputLensContext(context.toLensContextType(getExportType(trace, result))); + } + result.computeStatusIfUnknown(); + } + } + + public void refreshContextAfterShadowNotFound(LensContext context, LensProjectionContext projCtx, Collection> options, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + if (projCtx.isDelete()){ + //this is OK, shadow was deleted, but we will continue in processing with old shadow..and set it as full so prevent from other full loading + projCtx.setFullShadow(true); + return; + } + + boolean compensated = false; + if (!GetOperationOptions.isDoNotDiscovery(SelectorOptions.findRootOptions(options))) { + // The account might have been re-created by the discovery. + // Reload focus, try to find out if there is a new matching link (and the old is gone) + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext != null) { + Class focusClass = focusContext.getObjectTypeClass(); + if (FocusType.class.isAssignableFrom(focusClass)) { + LOGGER.trace("Reloading focus to check for new links"); + PrismObject focusCurrent; + try { + focusCurrent = cacheRepositoryService.getObject(focusContext.getObjectTypeClass(), focusContext.getOid(), null, result); + } catch (ObjectNotFoundException e) { + if (focusContext.isDelete()) { + // This may be OK. This may be later wave and the focus may be already deleted. + // Therefore let's just keep what we have. We have no way how to refresh context + // in that situation. + result.muteLastSubresultError(); + LOGGER.trace("ObjectNotFound error is not compensated (focus already deleted), setting context to tombstone"); + projCtx.markTombstone(); + return; + } else { + throw e; + } + } + FocusType focusType = (FocusType) focusCurrent.asObjectable(); + for (ObjectReferenceType linkRef: focusType.getLinkRef()) { + if (linkRef.getOid().equals(projCtx.getOid())) { + // The deleted shadow is still in the linkRef. This should not happen, but it obviously happens sometimes. + // Maybe some strange race condition? Anyway, we want a robust behavior and this linkRef should NOT be there. + // So simple remove it. + LOGGER.warn("The OID "+projCtx.getOid()+" of deleted shadow still exists in the linkRef after discovery ("+focusCurrent+"), removing it"); + ReferenceDelta unlinkDelta = prismContext.deltaFactory().reference().createModificationDelete( + FocusType.F_LINK_REF, focusContext.getObjectDefinition(), linkRef.asReferenceValue().clone()); + focusContext.swallowToSecondaryDelta(unlinkDelta); + continue; + } + boolean found = false; + for (LensProjectionContext pCtx: context.getProjectionContexts()) { + if (linkRef.getOid().equals(pCtx.getOid())) { + found = true; + break; + } + } + if (!found) { + // This link is new, it is not in the existing lens context + PrismObject newLinkRepoShadow = cacheRepositoryService.getObject(ShadowType.class, linkRef.getOid(), null, result); + if (ShadowUtil.matches(newLinkRepoShadow, projCtx.getResourceShadowDiscriminator())) { + LOGGER.trace("Found new matching link: {}, updating projection context", newLinkRepoShadow); + LOGGER.trace("Applying definition from provisioning first."); // MID-3317 + provisioningService.applyDefinition(newLinkRepoShadow, task, result); + projCtx.setObjectCurrent(newLinkRepoShadow); + projCtx.setOid(newLinkRepoShadow.getOid()); + projCtx.recompute(); + compensated = true; + break; + } else { + LOGGER.trace("Found new link: {}, but skipping it because it does not match the projection context", newLinkRepoShadow); + } + } + } + } + } + + } + + if (!compensated) { + LOGGER.trace("ObjectNotFound error is not compensated, setting context to tombstone"); + projCtx.markTombstone(); + } + } + + private void applyAttributesToGet(LensProjectionContext projCtx, Collection> options) throws SchemaException { + if ( !LensUtil.isPasswordReturnedByDefault(projCtx) + && LensUtil.needsFullShadowForCredentialProcessing(projCtx)) { + options.add(SelectorOptions.create(prismContext.toUniformPath(SchemaConstants.PATH_PASSWORD_VALUE), GetOperationOptions.createRetrieve())); + } + } + + public void reloadSecurityPolicyIfNeeded(@NotNull LensContext context, + @NotNull LensFocusContext focusContext, Task task, OperationResult result) + throws ExpressionEvaluationException, SchemaException, + CommunicationException, ConfigurationException, SecurityViolationException { + if (focusContext.hasOrganizationalChange()) { + loadSecurityPolicy(context, true, task, result); + } + } + + private void loadSecurityPolicy(LensContext context, + Task task, OperationResult result) throws ExpressionEvaluationException, + SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { + loadSecurityPolicy(context, false, task, result); + } + + @SuppressWarnings("unchecked") + private void loadSecurityPolicy(LensContext context, boolean forceReload, + Task task, OperationResult result) throws ExpressionEvaluationException, + SchemaException, CommunicationException, ConfigurationException, SecurityViolationException { + LensFocusContext genericFocusContext = context.getFocusContext(); + if (genericFocusContext == null || !genericFocusContext.represents(FocusType.class)) { + LOGGER.trace("Skipping load of security policy because focus is not of FocusType"); + return; + } + LensFocusContext focusContext = (LensFocusContext) genericFocusContext; + PrismObject focus = focusContext.getObjectAny(); + SecurityPolicyType globalSecurityPolicy = determineAndSetGlobalSecurityPolicy(context, focus, task, result); + SecurityPolicyType focusSecurityPolicy = determineAndSetFocusSecurityPolicy(focusContext, focus, globalSecurityPolicy, + forceReload, task, result); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Security policy:\n Global:\n{}\n Focus:\n{}", + globalSecurityPolicy.asPrismObject().debugDump(2), + focusSecurityPolicy==null?null:focusSecurityPolicy.asPrismObject().debugDump(2)); + } else { + LOGGER.debug("Security policy: global: {}, focus: {}", globalSecurityPolicy, focusSecurityPolicy); + } + } + + @NotNull + private SecurityPolicyType determineAndSetGlobalSecurityPolicy(LensContext context, + PrismObject focus, Task task, OperationResult result) + throws CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + SecurityPolicyType existingPolicy = context.getGlobalSecurityPolicy(); + if (existingPolicy != null) { + return existingPolicy; + } else { + SecurityPolicyType loadedPolicy = securityHelper.locateGlobalSecurityPolicy(focus, context.getSystemConfiguration(), + task, result); + SecurityPolicyType resultingPolicy; + if (loadedPolicy != null) { + resultingPolicy = loadedPolicy; + } else { + // use empty policy to avoid repeated lookups + resultingPolicy = new SecurityPolicyType(); + } + context.setGlobalSecurityPolicy(resultingPolicy); + return resultingPolicy; + } + } + + private SecurityPolicyType determineAndSetFocusSecurityPolicy(LensFocusContext focusContext, + PrismObject focus, SecurityPolicyType globalSecurityPolicy, boolean forceReload, Task task, + OperationResult result) throws SchemaException { + SecurityPolicyType existingPolicy = focusContext.getSecurityPolicy(); + if (existingPolicy != null && !forceReload) { + return existingPolicy; + } else { + SecurityPolicyType loadedPolicy = securityHelper.locateFocusSecurityPolicy(focus, task, result); + SecurityPolicyType resultingPolicy; + if (loadedPolicy != null) { + resultingPolicy = loadedPolicy; + } else { + // Not very clean. In fact we should store focus security policy separate from global + // policy to avoid confusion. But need to do this to fix MID-4793 and backport the fix. + // Therefore avoiding big changes. TODO: fix properly later + resultingPolicy = globalSecurityPolicy; + } + focusContext.setSecurityPolicy(resultingPolicy); + return resultingPolicy; + } + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/OutboundProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/OutboundProcessor.java index 83e193d0b9f..b3da95829bd 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/OutboundProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/OutboundProcessor.java @@ -228,6 +228,7 @@ private Ma mappingBuilder.rootNode(focusOdo); mappingBuilder.originType(OriginType.OUTBOUND); mappingBuilder.mappingKind(MappingKindType.OUTBOUND); + mappingBuilder.implicitTargetPath(ItemPath.create(ShadowType.F_ATTRIBUTES, attributeQName)); mappingBuilder.refinedObjectClassDefinition(rOcDef); if (projCtx.isDelete()) { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/ProjectionCredentialsProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/ProjectionCredentialsProcessor.java index c93ee3127db..0477a713067 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/ProjectionCredentialsProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/ProjectionCredentialsProcessor.java @@ -181,7 +181,9 @@ public ValuePolicyType resolve() { MappingInitializer,PrismPropertyDefinition> initializer = (builder) -> { - builder.mappingKind(MappingKindType.OUTBOUND); + builder.mappingKind(MappingKindType.OUTBOUND) + .implicitSourcePath(SchemaConstants.PATH_PASSWORD_VALUE) + .implicitTargetPath(SchemaConstants.PATH_PASSWORD_VALUE); builder.defaultTargetDefinition(projPasswordPropertyDefinition); builder.defaultSource(new Source<>(focusPasswordIdi, ExpressionConstants.VAR_INPUT_QNAME)); builder.valuePolicyResolver(stringPolicyResolver); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/InboundProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/InboundProcessor.java index fca63d3ad7b..e723fae42f0 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/InboundProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/InboundProcessor.java @@ -723,6 +723,7 @@ private vo .variableResolver(variableProducer) .valuePolicyResolver(createStringPolicyResolver(context)) .mappingKind(MappingKindType.INBOUND) + .implicitSourcePath(ShadowType.F_ATTRIBUTES.append(accountAttributeQName)) .originType(OriginType.INBOUND) .originObject(resource); @@ -1317,6 +1318,8 @@ private void processSpecialPropertyInbound(Collection void evaluateOutboundMapping(final LensContext