diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/progress/ProgressReporter.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/progress/ProgressReporter.java index f2d276f3dec..ff16cd4f5c2 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/progress/ProgressReporter.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/progress/ProgressReporter.java @@ -53,7 +53,7 @@ public class ProgressReporter implements ProgressListener { private Map nameCache = new HashMap<>(); private ProgressDto progress = new ProgressDto(); - private boolean abortRequested; + private volatile boolean abortRequested; // Operation result got from the asynchronous operation (null if async op not yet finished) private OperationResult asyncOperationResult; diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/internals/InternalsConfig.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/internals/InternalsConfig.java index 8c7f87e861f..fd700e2f872 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/internals/InternalsConfig.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/internals/InternalsConfig.java @@ -54,7 +54,7 @@ public class InternalsConfig { */ private static TestingPaths testingPaths = null; - private static boolean detailedAuhotizationLog = false; + private static boolean detailedAuthorizationLog = false; public static boolean isPrismMonitoring() { return prismMonitoring; @@ -120,12 +120,12 @@ public static void setTestingPaths(TestingPaths testingPaths) { InternalsConfig.testingPaths = testingPaths; } - public static boolean isDetailedAuhotizationLog() { - return detailedAuhotizationLog; + public static boolean isDetailedAuthorizationLog() { + return detailedAuthorizationLog; } - public static void setDetailedAuhotizationLog(boolean detailedAuhotizationLog) { - InternalsConfig.detailedAuhotizationLog = detailedAuhotizationLog; + public static void setDetailedAuthorizationLog(boolean detailedAuthorizationLog) { + InternalsConfig.detailedAuthorizationLog = detailedAuthorizationLog; } public static boolean isAllowClearDataLogging() { @@ -159,7 +159,7 @@ public static void set(Configuration internalsConfig) { prismMonitoring = internalsConfig.getBoolean("prismMonitoring", prismMonitoring); modelProfiling = internalsConfig.getBoolean("modelProfiling", modelProfiling); // TODO: testingPaths - detailedAuhotizationLog = internalsConfig.getBoolean("detailedAuhotizationLog", detailedAuhotizationLog); + detailedAuthorizationLog = internalsConfig.getBoolean("detailedAuhotizationLog", detailedAuthorizationLog); } @@ -173,7 +173,7 @@ public static void reset() { prismMonitoring = false; modelProfiling = false; testingPaths = null; - detailedAuhotizationLog = false; + detailedAuthorizationLog = false; } public static void setDevelopmentMode() { diff --git a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/util/ClockworkInspector.java b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/util/ClockworkInspector.java index 58bd8aa7986..fed2e2f2827 100644 --- a/model/model-api/src/main/java/com/evolveum/midpoint/model/api/util/ClockworkInspector.java +++ b/model/model-api/src/main/java/com/evolveum/midpoint/model/api/util/ClockworkInspector.java @@ -13,7 +13,7 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; /** - * Interface used to intercept the SyncContext as it passes through the computation. + * Interface used to intercept the ModelContext as it passes through the computation. * * It is mostly used in tests. * diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Clockwork.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Clockwork.java index 47b24d73c9d..5da53f48e82 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Clockwork.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/Clockwork.java @@ -16,6 +16,11 @@ import java.util.Collection; import javax.xml.datatype.XMLGregorianCalendar; +import com.evolveum.midpoint.model.impl.lens.projector.Components; + +import com.evolveum.midpoint.model.impl.lens.projector.policy.PolicyRuleEnforcer; +import com.evolveum.midpoint.prism.delta.ReferenceDelta; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @@ -52,7 +57,6 @@ import com.evolveum.midpoint.schema.cache.CacheConfigurationManager; import com.evolveum.midpoint.schema.cache.CacheType; import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.internals.InternalsConfig; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.result.OperationResultBuilder; import com.evolveum.midpoint.schema.result.OperationResultStatus; @@ -95,6 +99,7 @@ public class Clockwork { @Autowired private Migrator migrator; @Autowired private ClockworkMedic medic; @Autowired private PolicyRuleScriptExecutor policyRuleScriptExecutor; + @Autowired private PolicyRuleEnforcer policyRuleEnforcer; @Autowired private PolicyRuleSuspendTaskExecutor policyRuleSuspendTaskExecutor; @Autowired private ClockworkAuthorizationHelper clockworkAuthorizationHelper; @Autowired private CacheConfigurationManager cacheConfigurationManager; @@ -124,9 +129,7 @@ public HookOperationMode run(LensContext context, Task } LOGGER.trace("Running clockwork for context {}", context); - if (InternalsConfig.consistencyChecks) { - context.checkConsistence(); - } + context.checkConsistenceIfNeeded(); int clicked = 0; ClockworkConflictResolver.Context conflictResolutionContext = new ClockworkConflictResolver.Context(); @@ -141,6 +144,8 @@ public HookOperationMode run(LensContext context, Task //enterDefaultSearchExpressionEvaluatorCache(); provisioningService.enterConstraintsCheckerCache(); + executeInitialChecks(context); + while (context.getState() != ModelState.FINAL) { int maxClicks = getMaxClicks(result); @@ -191,6 +196,46 @@ public HookOperationMode run(LensContext context, Task } } + private void executeInitialChecks(LensContext context) throws PolicyViolationException { + if (context.hasFocusOfType(AssignmentHolderType.class)) { + checkArchetypeRefDelta(context); + } + } + + /** + * This check was originally present in AssignmentHolderProcessor. But it refers to focus primary delta only; + * so it's sufficient to execute it on clockwork entry (unless primary delta is manipulated e.g. in a scripting hook). + */ + private void checkArchetypeRefDelta(LensContext context) throws PolicyViolationException { + ObjectDelta focusPrimaryDelta = context.getFocusContext().getPrimaryDelta(); + if (focusPrimaryDelta != null) { + ReferenceDelta archetypeRefDelta = focusPrimaryDelta.findReferenceModification(AssignmentHolderType.F_ARCHETYPE_REF); + if (archetypeRefDelta != null) { + // We want to allow this under special circumstances. E.g. we want be able to import user with archetypeRef. + // Otherwise we won't be able to export a user and re-import it again. + if (focusPrimaryDelta.isAdd()) { + String archetypeOidFromAssignments = LensUtil.determineExplicitArchetypeOidFromAssignments(focusPrimaryDelta.getObjectToAdd()); + if (archetypeOidFromAssignments == null) { + throw new PolicyViolationException("Attempt add archetypeRef without a matching assignment"); + } else { + boolean match = true; + for (PrismReferenceValue archetypeRefDeltaVal : archetypeRefDelta.getValuesToAdd()) { + if (!archetypeOidFromAssignments.equals(archetypeRefDeltaVal.getOid())) { + match = false; + } + } + if (match) { + return; + } else { + throw new PolicyViolationException("Attempt add archetypeRef that does not match assignment"); + } + } + } + throw new PolicyViolationException("Attempt to modify archetypeRef directly"); + } + } + } + // todo check authorization in this method private boolean startTracingIfRequested(LensContext context, Task task, OperationResultBuilder builder, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, @@ -246,6 +291,7 @@ public LensContext previewChanges(LensContext conte projector.projectAllWaves(context, "preview", task, result); clockworkHookHelper.invokePreview(context, task, result); + policyRuleEnforcer.execute(context); policyRuleSuspendTaskExecutor.execute(context, task, result); } catch (ConfigurationException | SecurityViolationException | ObjectNotFoundException | SchemaException | @@ -365,68 +411,20 @@ public HookOperationMode click(LensContext context, Ta context.generateRequestIdentifierIfNeeded(); // We need to do this BEFORE projection. If we would do that after projection // there will be secondary changes that are not part of the request. - clockworkAuditHelper.audit(context, AuditEventStage.REQUEST, task, result, parentResult); // we need to take the overall ("run" operation result) not the current one + clockworkAuditHelper.audit(context, AuditEventStage.REQUEST, task, result, parentResult); // we need to take the overall ("run" operation result) not the current one } - boolean recompute = false; - if (!context.isFresh()) { - LOGGER.trace("Context is not fresh -- forcing cleanup and recomputation"); - recompute = true; - } else if (context.getExecutionWave() > context.getProjectionWave()) { // should not occur - LOGGER.warn("Execution wave is greater than projection wave -- forcing cleanup and recomputation"); - recompute = true; - } else if (state == ModelState.PRIMARY && ModelExecuteOptions.getInitialPartialProcessing(context.getOptions()) != null) { - LOGGER.trace("Initial phase was run with initialPartialProcessing option -- forcing cleanup and recomputation"); - recompute = true; - } - - if (recompute) { - context.cleanup(); - LOGGER.trace("Running projector with cleaned-up context for execution wave {}", context.getExecutionWave()); - projector.project(context, "PROJECTOR ("+state+")", task, result); - } else if (context.getExecutionWave() == context.getProjectionWave()) { - LOGGER.trace("Resuming projector for execution wave {}", context.getExecutionWave()); - projector.resume(context, "PROJECTOR ("+state+")", task, result); - } else { - LOGGER.trace("Skipping projection because the context is fresh and projection for current wave has already run"); - } + projectIfNeeded(context, task, result); if (!context.isRequestAuthorized()) { clockworkAuthorizationHelper.authorizeContextRequest(context, task, result); } medic.traceContext(LOGGER, "CLOCKWORK (" + state + ")", "before processing", true, context, false); - if (InternalsConfig.consistencyChecks) { - try { - context.checkConsistence(); - } catch (IllegalStateException e) { - throw new IllegalStateException(e.getMessage()+" in clockwork, state="+state, e); - } - } - if (InternalsConfig.encryptionChecks && !ModelExecuteOptions.isNoCrypt(context.getOptions())) { - context.checkEncrypted(); - } + context.checkConsistenceIfNeeded(); + context.checkEncryptedIfNeeded(); - switch (state) { - case INITIAL: - processInitialToPrimary(context); - break; - case PRIMARY: - processPrimaryToSecondary(context, task, result); - break; - case SECONDARY: - if (context.getExecutionWave() > context.getMaxWave() + 1) { - processSecondaryToFinal(context, task, result); - } else { - processSecondary(context, task, result, parentResult); - } - break; - case FINAL: - HookOperationMode mode = processFinal(context, task, result, parentResult); - medic.clockworkFinish(context); - return mode; - } - return clockworkHookHelper.invokeHooks(context, task, result); + return moveStateForward(context, task, parentResult, result, state); } catch (CommunicationException | ConfigurationException | ExpressionEvaluationException | ObjectNotFoundException | PolicyViolationException | SchemaException | SecurityViolationException | RuntimeException | Error | @@ -445,21 +443,76 @@ public HookOperationMode click(LensContext context, Ta } } + private void projectIfNeeded(LensContext context, Task task, OperationResult result) + throws SchemaException, ConfigurationException, PolicyViolationException, ExpressionEvaluationException, + ObjectNotFoundException, ObjectAlreadyExistsException, CommunicationException, SecurityViolationException, + PreconditionViolationException { + boolean recompute = false; + if (!context.isFresh()) { + LOGGER.trace("Context is not fresh -- forcing cleanup and recomputation"); + recompute = true; + } else if (context.getExecutionWave() > context.getProjectionWave()) { // should not occur + LOGGER.warn("Execution wave is greater than projection wave -- forcing cleanup and recomputation"); + recompute = true; + } else if (context.isInPrimary() && ModelExecuteOptions.getInitialPartialProcessing(context.getOptions()) != null) { + LOGGER.trace("Initial phase was run with initialPartialProcessing option -- forcing cleanup and recomputation"); + recompute = true; + } + + if (recompute) { + context.cleanup(); + LOGGER.trace("Running projector with cleaned-up context for execution wave {}", context.getExecutionWave()); + projector.project(context, "PROJECTOR ("+ context.getState() +")", task, result); + } else if (context.getExecutionWave() == context.getProjectionWave()) { + LOGGER.trace("Resuming projector for execution wave {}", context.getExecutionWave()); + projector.resume(context, "PROJECTOR ("+ context.getState() +")", task, result); + } else { + LOGGER.trace("Skipping projection because the context is fresh and projection for current wave has already run"); + } + } + + private HookOperationMode moveStateForward(LensContext context, Task task, OperationResult parentResult, + OperationResult result, ModelState state) throws PolicyViolationException, ObjectNotFoundException, SchemaException, + ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException, + ExpressionEvaluationException, PreconditionViolationException { + switch (state) { + case INITIAL: + processInitialToPrimary(context); + break; + case PRIMARY: + processPrimaryToSecondary(context, task, result); + break; + case SECONDARY: + if (context.getExecutionWave() > context.getMaxWave() + 1) { + processSecondaryToFinal(context, task, result); + } else { + processSecondary(context, task, result, parentResult); + } + break; + case FINAL: + HookOperationMode mode = processFinal(context, task, result, parentResult); + medic.clockworkFinish(context); + return mode; + } + return clockworkHookHelper.invokeHooks(context, task, result); + } + private void switchState(LensContext context, ModelState newState) { medic.clockworkStateSwitch(context, newState); context.setState(newState); } - private void processInitialToPrimary(LensContext context) { - // Context loaded, nothing special do. Bump state to PRIMARY. + private void processInitialToPrimary(LensContext context) throws PolicyViolationException { + // To mimic operation of the original enforcer hook, we execute the following only in the initial state. + policyRuleEnforcer.execute(context); + switchState(context, ModelState.PRIMARY); } private void processPrimaryToSecondary(LensContext context, Task task, OperationResult result) throws PolicyViolationException, ObjectNotFoundException, SchemaException { - // Nothing to do now. The context is already recomputed. - switchState(context, ModelState.SECONDARY); - policyRuleSuspendTaskExecutor.execute(context, task, result); + + switchState(context, ModelState.SECONDARY); } private void processSecondary(LensContext context, Task task, OperationResult result, OperationResult overallResult) @@ -468,13 +521,13 @@ private void processSecondary(LensContext context, Tas Holder restartRequestedHolder = new Holder<>(false); - medic.partialExecute("execution", + medic.partialExecute(Components.EXECUTION, (result1) -> { boolean restartRequested = changeExecutor.executeChanges(context, task, result1); restartRequestedHolder.setValue(restartRequested); }, context.getPartialProcessingOptions()::getExecution, - Clockwork.class, context, result); + Clockwork.class, context, null, result); clockworkAuditHelper.audit(context, AuditEventStage.EXECUTION, task, result, overallResult); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkMedic.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkMedic.java index aba54f85b4c..aaef8cbbc39 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkMedic.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ClockworkMedic.java @@ -8,8 +8,12 @@ import java.util.function.Supplier; +import com.evolveum.midpoint.model.impl.lens.projector.ProjectorProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.util.*; import com.evolveum.midpoint.schema.cache.CacheConfigurationManager; +import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.xml.ns._public.common.common_3.ProjectorComponentTraceType; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -37,6 +41,8 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.PartialProcessingTypeType; +import javax.xml.datatype.XMLGregorianCalendar; + import static com.evolveum.midpoint.model.impl.lens.LensUtil.getExportType; /** @@ -146,12 +152,132 @@ public void afterMappingEvaluation(LensContext context } } - public void partialExecute(String componentName, ProjectorComponentRunnable runnable, - Supplier optionSupplier, - Class executingClass, LensContext context, OperationResult parentResult) + @SuppressWarnings({ "rawtypes", "UnusedReturnValue" }) + public boolean partialExecute(String componentName, ProjectorProcessor processor, + ProjectionAwareProcessorMethodRef method, Supplier optionSupplier, + Class executingClass, LensContext context, LensProjectionContext projectionContext, String activityDescription, + XMLGregorianCalendar now, Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PreconditionViolationException { - partialExecute(componentName, runnable, optionSupplier, executingClass, context, null, parentResult); + if (shouldExecute(componentName, processor, context, projectionContext)) { + partialExecute(componentName, (result1) -> { + //noinspection unchecked + method.run(context, projectionContext, activityDescription, now, task, result1); + }, optionSupplier, executingClass, context, projectionContext, parentResult); + return true; + } else { + return false; + } + } + + @SuppressWarnings("rawtypes") + public boolean partialExecute(String componentName, ProjectorProcessor processor, + ProcessorMethodRef method, Supplier optionSupplier, + Class executingClass, LensContext context, String activityDescription, + XMLGregorianCalendar now, Task task, OperationResult parentResult) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, + PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PreconditionViolationException { + if (shouldExecute(componentName, processor, context, null)) { + partialExecute(componentName, (result1) -> { + //noinspection unchecked + method.run(context, activityDescription, now, task, result1); + }, optionSupplier, executingClass, context, null, parentResult); + return true; + } else { + return false; + } + } + + @SuppressWarnings({ "UnusedReturnValue", "rawtypes" }) + public boolean partialExecute(String componentName, ProjectorProcessor processor, + SimplifiedProcessorMethodRef method, Supplier optionSupplier, + Class executingClass, LensContext context, + XMLGregorianCalendar now, Task task, OperationResult parentResult) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, + PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PreconditionViolationException { + if (shouldExecute(componentName, processor, context, null)) { + partialExecute(componentName, (result1) -> { + //noinspection unchecked + method.run(context, now, task, result1); + }, optionSupplier, executingClass, context, null, parentResult); + return true; + } else { + return false; + } + } + + private boolean shouldExecute(String componentName, ProjectorProcessor processor, LensContext context, LensProjectionContext projectionContext) throws SchemaException { + ProcessorExecution processorExecution = processor.getClass().getAnnotation(ProcessorExecution.class); + return processorExecution == null || focusPresenceAndTypeCheckPasses(componentName, context, processorExecution) + && focusDeletionCheckPasses(componentName, context.getFocusContext(), processorExecution) + && projectionDeletionCheckPasses(componentName, projectionContext, processorExecution); + } + + private boolean focusPresenceAndTypeCheckPasses(String componentName, LensContext context, + ProcessorExecution processorExecution) { + if (!processorExecution.focusRequired()) { + // intentionally skipping focus type check + return true; + } + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext == null) { + LOGGER.trace("Skipping {} processing because focus context is null", componentName); + return false; + } + Class requiredFocusType = processorExecution.focusType(); + if (!focusContext.isOfType(requiredFocusType)) { + LOGGER.trace("Skipping {} processing because focus context is not of required focus class ({})", + componentName, requiredFocusType.getSimpleName()); + return false; + } + return true; + } + + private boolean focusDeletionCheckPasses(String componentName, LensFocusContext focusContext, + ProcessorExecution processorExecution) throws SchemaException { + if (focusContext == null) { + // If we are OK with no focus context, then the deletion is irrelevant + return true; + } + SkipWhenFocusDeleted skipWhenFocusDeleted = processorExecution.skipWhenFocusDeleted(); + switch (skipWhenFocusDeleted) { + case NONE: + return true; + case PRIMARY_OR_SECONDARY: + return focusPrimaryDeletionCheckPasses(focusContext, componentName) + && focusSecondaryDeletionCheckPasses(focusContext, componentName); + case PRIMARY: + return focusPrimaryDeletionCheckPasses(focusContext, componentName); + default: + throw new AssertionError(skipWhenFocusDeleted); + } + } + + private boolean focusPrimaryDeletionCheckPasses(LensFocusContext focusContext, String componentName) { + if (focusContext.isDelete()) { + LOGGER.trace("Skipping '{}' because focus is being deleted (primary delta)", componentName); + return false; + } else { + return true; + } + } + + private boolean focusSecondaryDeletionCheckPasses(LensFocusContext focusContext, String componentName) throws SchemaException { + if (focusContext.isSecondaryDelete()) { + LOGGER.trace("Skipping '{}' because focus is being deleted (secondary delta)", componentName); + return false; + } else { + return true; + } + } + + private boolean projectionDeletionCheckPasses(String componentName, LensProjectionContext projectionContext, ProcessorExecution processorExecution) { + if (processorExecution.skipWhenProjectionDeleted() && projectionContext != null && projectionContext.isDelete()) { + LOGGER.trace("Skipping '{}' because projection is being deleted", componentName); + return false; + } else { + return true; + } } public void partialExecute(String baseComponentName, ProjectorComponentRunnable runnable, @@ -160,6 +286,8 @@ public void partialExecute(String baseComponentName, ProjectorComponentRunnable throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PreconditionViolationException { + context.checkAbortRequested(); + OperationResult parentResult; if (initialParentResult == null) { LOGGER.warn("No parentResult in ClockworkMedic.partialExecute! Creating dummy one"); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java index 49678098608..ab0954ef30d 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensContext.java @@ -27,6 +27,7 @@ import com.evolveum.midpoint.schema.ObjectTreeDeltas; import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; import com.evolveum.midpoint.schema.expression.ExpressionProfile; +import com.evolveum.midpoint.schema.internals.InternalsConfig; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.MiscSchemaUtil; import com.evolveum.midpoint.schema.util.ObjectDeltaSchemaLevelUtil; @@ -193,7 +194,7 @@ public enum ExportType { * Moved from ProjectionValuesProcessor TODO consider if necessary to * serialize to XML */ - private List conflictingProjectionContexts = new ArrayList<>(); + @NotNull private final List conflictingProjectionContexts = new ArrayList<>(); transient private boolean preview; @@ -432,6 +433,17 @@ public int getMaxWave() { return maxWave; } + // TODO This method is currently used only when previewing changes. Is that OK? + public int computeMaxWaves() { + if (getPartialProcessingOptions().getInbound() != PartialProcessingTypeType.SKIP) { + // Let's do one extra wave with no accounts in it. This time we expect to get the results of the execution to the user + // via inbound, e.g. identifiers generated by the resource, DNs and similar things. Hence the +2 instead of +1 + return getMaxWave() + 2; + } else { + return getMaxWave() + 1; + } + } + public boolean isFresh() { return isFresh; } @@ -787,10 +799,19 @@ public void refreshAuxiliaryObjectClassDefinitions() throws SchemaException { public void checkAbortRequested() { if (isAbortRequested()) { - throw new RuntimeException("Aborted on user request"); // TODO more - // meaningful - // exception - // + message + throw new RuntimeException("Aborted on user request"); // TODO more meaningful exception + message + } + } + + public void checkConsistenceIfNeeded() { + if (InternalsConfig.consistencyChecks) { + try { + checkConsistence(); + } catch (IllegalStateException e) { + throw new IllegalStateException(e.getMessage()+" in clockwork, state="+state, e); + } + } else { + checkAbortRequested(); // useful to check as often as realistically possible } } @@ -805,6 +826,12 @@ public void checkConsistence() { } } + public void checkEncryptedIfNeeded() { + if (InternalsConfig.encryptionChecks && !ModelExecuteOptions.isNoCrypt(options)) { + checkEncrypted(); + } + } + public void checkEncrypted() { if (focusContext != null && !focusContext.isDelete()) { focusContext.checkEncrypted(); @@ -904,10 +931,8 @@ public void normalize() { if (focusContext != null) { focusContext.normalize(); } - if (projectionContexts != null) { - for (LensProjectionContext projectionContext : projectionContexts) { - projectionContext.normalize(); - } + for (LensProjectionContext projectionContext : projectionContexts) { + projectionContext.normalize(); } } @@ -1415,6 +1440,7 @@ public void setSequenceCounter(String sequenceOid, long counter) { sequences.put(sequenceOid, counter); } + @NotNull public List getConflictingProjectionContexts() { return conflictingProjectionContexts; } @@ -1648,4 +1674,16 @@ ObjectDeltaSchemaLevelUtil.NameResolver getNameResolver() { return null; }; } + + public void removeIgnoredContexts() { + projectionContexts.removeIf(projCtx -> projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.IGNORE); + } + + public boolean isInPrimary() { + return state == ModelState.PRIMARY; + } + + public boolean hasFocusOfType(Class type) { + return focusContext != null && focusContext.isOfType(type); + } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensFocusContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensFocusContext.java index 58a13b23884..dd0050e839f 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensFocusContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensFocusContext.java @@ -60,10 +60,6 @@ private int getProjectionWave() { return getLensContext().getProjectionWave(); } - private int getExecutionWave() { - return getLensContext().getProjectionWave(); - } - public ArchetypePolicyType getArchetypePolicyType() { return archetypePolicyType; } @@ -105,6 +101,11 @@ public boolean isDelete() { return ObjectDelta.isDelete(getPrimaryDelta()); } + boolean isSecondaryDelete() throws SchemaException { + ObjectDelta userSecondaryDelta = getProjectionWaveSecondaryDelta(); + return userSecondaryDelta != null && ChangeType.DELETE.equals(userSecondaryDelta.getChangeType()); + } + public boolean isAdd() { return ObjectDelta.isAdd(getPrimaryDelta()); } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java index 8b8f4ef0e58..0f8da857c39 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java @@ -1,1507 +1,1517 @@ -/* - * Copyright (c) 2010-2019 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; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.function.Consumer; - -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.common.refinery.*; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.ItemDelta; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.prism.polystring.PolyString; -import com.evolveum.midpoint.schema.DeltaConvertor; -import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.util.exception.*; -import com.evolveum.midpoint.util.logging.LoggingUtils; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; - -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang3.BooleanUtils; -import org.jvnet.jaxb2_commons.lang.Validate; - -import com.evolveum.midpoint.common.crypto.CryptoUtil; -import com.evolveum.midpoint.model.api.context.ModelProjectionContext; -import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; -import com.evolveum.midpoint.prism.delta.ChangeType; -import com.evolveum.midpoint.prism.delta.DeltaSetTriple; -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.prism.delta.ReferenceDelta; -import com.evolveum.midpoint.prism.util.ObjectDeltaObject; -import com.evolveum.midpoint.schema.processor.ResourceAttribute; -import com.evolveum.midpoint.schema.processor.ResourceAttributeContainer; -import com.evolveum.midpoint.schema.processor.ResourceSchema; -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.schema.util.ResourceTypeUtil; -import com.evolveum.midpoint.schema.util.SchemaDebugUtil; -import com.evolveum.midpoint.util.Cloner; -import com.evolveum.midpoint.util.DebugUtil; - -/** - * @author semancik - * - */ -public class LensProjectionContext extends LensElementContext implements ModelProjectionContext { - - private static final Trace LOGGER = TraceManager.getTrace(LensProjectionContext.class); - - private ObjectDelta syncDelta; - - /** - * Is this projection the source of the synchronization? (The syncDelta attribute could be used for this but in - * reality it is not always present.) We need this information e.g. when it's not possible to record a clockwork - * exception to focus (e.g. as in MID-5801). The alternate way is to record it into shadow representing the synchronization - * source, e.g. the object being imported, reconciled, or live-synced. - */ - private boolean synchronizationSource; - - private ObjectDelta secondaryDelta; - - /** - * If set to true: absolute state of this projection was detected by the synchronization. - * This is mostly for debugging and visibility. It is not used by projection logic. - */ - private boolean syncAbsoluteTrigger = false; - - /** - * The wave in which this resource should be processed. Initial value of -1 means "undetermined". - */ - private int wave = -1; - - /** - * Indicates that the wave computation is still in progress. - */ - private transient boolean waveIncomplete = false; - - /** - * Definition of account type. - */ - private ResourceShadowDiscriminator resourceShadowDiscriminator; - - private boolean fullShadow = false; - - /** - * True if the account is assigned to the user by a valid assignment. It may be false for accounts that are either - * found to be illegal by live sync, were unassigned from user, etc. - * If set to null the situation is not yet known. Null is a typical value when the context is constructed. - */ - private boolean isAssigned; - private boolean isAssignedOld; - - /** - * True if the account should be part of the synchronization. E.g. outbound expression should be applied to it. - */ - private boolean isActive; - - /** - * True if there is a valid assignment for this projection and/or the policy allows such projection to exist. - */ - private Boolean isLegal = null; - private Boolean isLegalOld = null; - - /** - * True if the projection exists (or will exist) on resource. False if it does not exist. - * NOTE: entire projection is loaded with pointInTime=future. Therefore this does NOT - * reflect actual situation. If there is a pending operation to create the object then - * isExists will in fact be true. - */ - private boolean isExists; - - /** - * True if shadow exists in the repo. It is set to false after projector discovers that a shadow is gone. - * This is a corner case, but it may happen: if shadow is unintentionally deleted, if the shadow is - * cleaned up by another thread and so on. - */ - private transient boolean shadowExistsInRepo = true; - - /** - * Decision regarding the account. It indicated what the engine has DECIDED TO DO with the context. - * If set to null no decision was made yet. Null is also a typical value when the context is created. - */ - private SynchronizationPolicyDecision synchronizationPolicyDecision; - - /** - * True if we want to reconcile account in this context. - */ - private boolean doReconciliation; - - /** - * false if the context should be not taken into the account while synchronizing changes from other resource - */ - private boolean canProject = true; - - /** - * Synchronization situation as it was originally detected by the synchronization code (SynchronizationService). - * This is mostly for debug purposes. Projector and clockwork do not need to care about this. - * The synchronization intent is used instead. - */ - private SynchronizationSituationType synchronizationSituationDetected = null; - /** - * Synchronization situation which was the result of synchronization reaction (projector and clockwork run). - * This is mostly for debug purposes. Projector and clockwork do not care about this (except for setting it). - * The synchronization decision is used instead. - */ - private SynchronizationSituationType synchronizationSituationResolved = null; - - /** - * Delta set triple for accounts. Specifies which accounts should be added, removed or stay as they are. - * It tells almost nothing about attributes directly although the information about attributes are inside - * each account construction (in a form of ValueConstruction that contains attribute delta triples). - * - * Intermediary computation result. It is stored to allow re-computing of account constructions during - * iterative computations. - * - * Source: AssignmentProcessor - * Target: ConsolidationProcessor / ReconciliationProcessor (via squeezed structures) - */ - private transient PrismValueDeltaSetTriple> constructionDeltaSetTriple; - - /** - * Triples for outbound mappings; similar to the above. - * Source: OutboundProcessor - * Target: ConsolidationProcessor / ReconciliationProcessor (via squeezed structures) - */ - private transient Construction outboundConstruction; - - /** - * Postprocessed triples from the above two properties. - * Source: ConsolidationProcessor - * Target: ReconciliationProcessor - */ - private transient Map,PrismPropertyDefinition>>> squeezedAttributes; - private transient Map,PrismContainerDefinition>>> squeezedAssociations; - private transient Map,PrismPropertyDefinition>>> squeezedAuxiliaryObjectClasses; - - private transient Collection dependencies = null; - - // Cached copy, to avoid constructing it over and over again - private transient PrismObjectDefinition shadowDefinition = null; - - private transient RefinedObjectClassDefinition structuralObjectClassDefinition; - private transient Collection auxiliaryObjectClassDefinitions; - private transient CompositeRefinedObjectClassDefinition compositeObjectClassDefinition; - - private SecurityPolicyType projectionSecurityPolicy; - - /** - * Resource that hosts this projection. - */ - transient private ResourceType resource; - - /** - * EXPERIMENTAL. A flag that this projection context has to be put into 'history archive'. - * Necessary to evaluate old state of hasLinkedAccount. - * - * TODO implement as non-transient. - */ - transient private boolean toBeArchived; - - transient private String humanReadableName; - - private Map> entitlementMap = new HashMap<>(); - - transient private String humanReadableString; - - LensProjectionContext(LensContext lensContext, ResourceShadowDiscriminator resourceAccountType) { - super(ShadowType.class, lensContext); - this.resourceShadowDiscriminator = resourceAccountType; - this.isAssigned = false; - this.isAssignedOld = false; - } - - public ObjectDelta getSyncDelta() { - return syncDelta; - } - - public void setSyncDelta(ObjectDelta syncDelta) { - this.syncDelta = syncDelta; - } - - @Override - public ObjectDelta getSecondaryDelta() { - return secondaryDelta; - } - - @Override - public Collection> getAllDeltas() { - List> deltas = new ArrayList<>(2); - ObjectDelta primaryDelta = getPrimaryDelta(); - if (primaryDelta != null) { - deltas.add(primaryDelta); - } - if (secondaryDelta != null) { - deltas.add(secondaryDelta); - } - return deltas; - } - - @Override - public ObjectDeltaObject getObjectDeltaObject() throws SchemaException { - return new ObjectDeltaObject<>(getObjectCurrent(), getDelta(), getObjectNew(), getObjectDefinition()); - } - - public boolean hasSecondaryDelta() { - return secondaryDelta != null && !secondaryDelta.isEmpty(); - } - - @Override - public void setSecondaryDelta(ObjectDelta secondaryDelta) { - this.secondaryDelta = secondaryDelta; - } - - public void addSecondaryDelta(ObjectDelta delta) throws SchemaException { - if (secondaryDelta == null) { - secondaryDelta = delta; - } else { - secondaryDelta.merge(delta); - } - } - - @Override - public void swallowToSecondaryDelta(ItemDelta itemDelta) throws SchemaException { - if (secondaryDelta == null) { - secondaryDelta = getPrismContext().deltaFactory().object().create(getObjectTypeClass(), ChangeType.MODIFY); - secondaryDelta.setOid(getOid()); - } - LensUtil.setDeltaOldValue(this, itemDelta); - secondaryDelta.swallow(itemDelta); - } - - @Override - public void deleteSecondaryDeltas() { - secondaryDelta = null; - } - - @Override - public void setOid(String oid) { - super.setOid(oid); - if (secondaryDelta != null) { - secondaryDelta.setOid(oid); - } - } - - public boolean isSyncAbsoluteTrigger() { - return syncAbsoluteTrigger; - } - - public void setSyncAbsoluteTrigger(boolean syncAbsoluteTrigger) { - this.syncAbsoluteTrigger = syncAbsoluteTrigger; - } - - public int getWave() { - return wave; - } - - public void setWave(int wave) { - this.wave = wave; - } - - public boolean isWaveIncomplete() { - return waveIncomplete; - } - - public void setWaveIncomplete(boolean waveIncomplete) { - this.waveIncomplete = waveIncomplete; - } - - public boolean isDoReconciliation() { - return doReconciliation; - } - - public void setDoReconciliation(boolean doReconciliation) { - this.doReconciliation = doReconciliation; - } - - @Override - public ResourceShadowDiscriminator getResourceShadowDiscriminator() { - return resourceShadowDiscriminator; - } - - public void markTombstone() { - if (resourceShadowDiscriminator != null) { - resourceShadowDiscriminator.setTombstone(true); - } - setExists(false); - setFullShadow(false); - humanReadableName = null; - } - - public void setResourceShadowDiscriminator(ResourceShadowDiscriminator resourceShadowDiscriminator) { - this.resourceShadowDiscriminator = resourceShadowDiscriminator; - } - - public boolean compareResourceShadowDiscriminator(ResourceShadowDiscriminator rsd, boolean compareOrder) { - Validate.notNull(rsd.getResourceOid()); - if (resourceShadowDiscriminator == null) { - // This may be valid case e.g. in case of broken contexts or if a context is just loading - return false; - } - if (!rsd.getResourceOid().equals(resourceShadowDiscriminator.getResourceOid())) { - return false; - } - if (!rsd.getKind().equals(resourceShadowDiscriminator.getKind())) { - return false; - } - if (rsd.isTombstone() != resourceShadowDiscriminator.isTombstone()) { - return false; - } - if (rsd.getIntent() == null) { - try { - if (!getStructuralObjectClassDefinition().isDefaultInAKind()) { - return false; - } - } catch (SchemaException e) { - throw new SystemException("Internal error: "+e.getMessage(), e); - } - } else if (!rsd.getIntent().equals(resourceShadowDiscriminator.getIntent())) { - return false; - } - if (!Objects.equals(rsd.getTag(), resourceShadowDiscriminator.getTag())) { - return false; - } - - if (compareOrder && rsd.getOrder() != resourceShadowDiscriminator.getOrder()) { - return false; - } - - return true; - } - - public boolean isTombstone() { - if (resourceShadowDiscriminator == null) { - return false; - } - return resourceShadowDiscriminator.isTombstone(); - } - - public void addAccountSyncDelta(ObjectDelta delta) throws SchemaException { - if (syncDelta == null) { - syncDelta = delta; - } else { - syncDelta.merge(delta); - } - } - - public boolean isAdd() { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.ADD) { - return true; - } else if (synchronizationPolicyDecision != null) { - return false; - } else { - return ObjectDelta.isAdd(getPrimaryDelta()) || ObjectDelta.isAdd(getSecondaryDelta()); - } - } - - public boolean isModify() { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.KEEP) { - return true; - } else if (synchronizationPolicyDecision != null) { - return false; - } else { - return super.isModify(); - } - } - - public boolean isDelete() { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.DELETE) { - return true; - } else if (synchronizationPolicyDecision != null) { - return false; - } else { - return ObjectDelta.isDelete(syncDelta) || ObjectDelta.isDelete(getPrimaryDelta()) || ObjectDelta.isDelete(getSecondaryDelta()); - } - } - - @Override - public ArchetypeType getArchetype() { - throw new UnsupportedOperationException("Archetypes are not supported for projections."); - } - - public ResourceType getResource() { - return resource; - } - - public void setResource(ResourceType resource) { - this.resource = resource; - } - - public Map> getEntitlementMap() { - return entitlementMap; - } - - public void setEntitlementMap(Map> entitlementMap) { - this.entitlementMap = entitlementMap; - } - - @Override - public PrismObjectDefinition getObjectDefinition() { - if (shadowDefinition == null) { - try { - shadowDefinition = ShadowUtil.applyObjectClass(super.getObjectDefinition(), getCompositeObjectClassDefinition()); - } catch (SchemaException e) { - // This should not happen - throw new SystemException(e.getMessage(), e); - } - } - return shadowDefinition; - } - - public boolean isAssigned() { - return isAssigned; - } - - public void setAssigned(boolean isAssigned) { - this.isAssigned = isAssigned; - } - - public boolean isAssignedOld() { - return isAssignedOld; - } - - public void setAssignedOld(boolean isAssignedOld) { - this.isAssignedOld = isAssignedOld; - } - - public boolean isActive() { - return isActive; - } - - public void setActive(boolean isActive) { - this.isActive = isActive; - } - - public Boolean isLegal() { - return isLegal; - } - - public void setLegal(Boolean isLegal) { - this.isLegal = isLegal; - } - - public Boolean isLegalOld() { - return isLegalOld; - } - - public void setLegalOld(Boolean isLegalOld) { - this.isLegalOld = isLegalOld; - } - - public boolean isExists() { - return isExists; - } - - public void setExists(boolean exists) { - this.isExists = exists; - } - - public boolean isShadowExistsInRepo() { - return shadowExistsInRepo; - } - - public void setShadowExistsInRepo(boolean shadowExistsInRepo) { - this.shadowExistsInRepo = shadowExistsInRepo; - } - - public SynchronizationPolicyDecision getSynchronizationPolicyDecision() { - return synchronizationPolicyDecision; - } - - public void setSynchronizationPolicyDecision(SynchronizationPolicyDecision policyDecision) { - this.synchronizationPolicyDecision = policyDecision; - } - - public SynchronizationSituationType getSynchronizationSituationDetected() { - return synchronizationSituationDetected; - } - - public void setSynchronizationSituationDetected( - SynchronizationSituationType synchronizationSituationDetected) { - this.synchronizationSituationDetected = synchronizationSituationDetected; - } - - public SynchronizationSituationType getSynchronizationSituationResolved() { - return synchronizationSituationResolved; - } - - void setSynchronizationSituationResolved(SynchronizationSituationType synchronizationSituationResolved) { - this.synchronizationSituationResolved = synchronizationSituationResolved; - } - - public boolean isFullShadow() { - return fullShadow; - } - - /** - * Returns true if full shadow is available, either loaded or in a create delta. - */ - public boolean hasFullShadow() { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.ADD) { - return true; - } - return isFullShadow(); - } - - public void setFullShadow(boolean fullShadow) { - this.fullShadow = fullShadow; - } - - public ShadowKindType getKind() { - ResourceShadowDiscriminator discr = getResourceShadowDiscriminator(); - if (discr != null) { - return discr.getKind(); - } - if (getObjectOld()!=null) { - return getObjectOld().asObjectable().getKind(); - } - if (getObjectCurrent()!=null) { - return getObjectCurrent().asObjectable().getKind(); - } - if (getObjectNew()!=null) { - return getObjectNew().asObjectable().getKind(); - } - return ShadowKindType.ACCOUNT; - } - - public PrismValueDeltaSetTriple> getConstructionDeltaSetTriple() { - return constructionDeltaSetTriple; - } - - public void setConstructionDeltaSetTriple( - PrismValueDeltaSetTriple> constructionDeltaSetTriple) { - this.constructionDeltaSetTriple = constructionDeltaSetTriple; - } - - public Construction getOutboundConstruction() { - return outboundConstruction; - } - - public void setOutboundConstruction(Construction outboundConstruction) { - this.outboundConstruction = outboundConstruction; - } - - public Map,PrismPropertyDefinition>>> getSqueezedAttributes() { - return squeezedAttributes; - } - - public void setSqueezedAttributes(Map,PrismPropertyDefinition>>> squeezedAttributes) { - this.squeezedAttributes = squeezedAttributes; - } - - public Map,PrismContainerDefinition>>> getSqueezedAssociations() { - return squeezedAssociations; - } - - public void setSqueezedAssociations( - Map,PrismContainerDefinition>>> squeezedAssociations) { - this.squeezedAssociations = squeezedAssociations; - } - - public Map, PrismPropertyDefinition>>> getSqueezedAuxiliaryObjectClasses() { - return squeezedAuxiliaryObjectClasses; - } - - public void setSqueezedAuxiliaryObjectClasses( - Map, PrismPropertyDefinition>>> squeezedAuxiliaryObjectClasses) { - this.squeezedAuxiliaryObjectClasses = squeezedAuxiliaryObjectClasses; - } - - public ResourceObjectTypeDefinitionType getResourceObjectTypeDefinitionType() { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.BROKEN) { - return null; - } - ResourceShadowDiscriminator discr = getResourceShadowDiscriminator(); - if (discr == null) { - return null; // maybe when an account is deleted - } - if (resource == null) { - return null; - } - ResourceObjectTypeDefinitionType def = ResourceTypeUtil.getResourceObjectTypeDefinitionType(resource, discr.getKind(), discr.getIntent()); - return def; - } - - private ResourceSchema getResourceSchema() throws SchemaException { - return RefinedResourceSchemaImpl.getResourceSchema(resource, getNotNullPrismContext()); - } - - public RefinedResourceSchema getRefinedResourceSchema() throws SchemaException { - if (resource == null) { - return null; - } - return RefinedResourceSchemaImpl.getRefinedSchema(resource, LayerType.MODEL, getNotNullPrismContext()); - } - - public RefinedObjectClassDefinition getStructuralObjectClassDefinition() throws SchemaException { - if (structuralObjectClassDefinition == null) { - RefinedResourceSchema refinedSchema = getRefinedResourceSchema(); - if (refinedSchema == null) { - return null; - } - structuralObjectClassDefinition = refinedSchema.getRefinedDefinition(getResourceShadowDiscriminator().getKind(), getResourceShadowDiscriminator().getIntent()); - } - return structuralObjectClassDefinition; - } - - public Collection getAuxiliaryObjectClassDefinitions() throws SchemaException { - if (auxiliaryObjectClassDefinitions == null) { - refreshAuxiliaryObjectClassDefinitions(); - } - return auxiliaryObjectClassDefinitions; - } - - public void refreshAuxiliaryObjectClassDefinitions() throws SchemaException { - RefinedResourceSchema refinedSchema = getRefinedResourceSchema(); - if (refinedSchema == null) { - return; - } - List auxiliaryObjectClassQNames = new ArrayList<>(); - addAuxiliaryObjectClassNames(auxiliaryObjectClassQNames, getObjectOld()); - addAuxiliaryObjectClassNames(auxiliaryObjectClassQNames, getObjectNew()); - auxiliaryObjectClassDefinitions = new ArrayList<>(auxiliaryObjectClassQNames.size()); - for (QName auxiliaryObjectClassQName: auxiliaryObjectClassQNames) { - RefinedObjectClassDefinition auxiliaryObjectClassDef = refinedSchema.getRefinedDefinition(auxiliaryObjectClassQName); - if (auxiliaryObjectClassDef == null) { - throw new SchemaException("Auxiliary object class "+auxiliaryObjectClassQName+" specified in "+this+" does not exist"); - } - auxiliaryObjectClassDefinitions.add(auxiliaryObjectClassDef); - } - compositeObjectClassDefinition = null; - } - - public CompositeRefinedObjectClassDefinition getCompositeObjectClassDefinition() throws SchemaException { - if (compositeObjectClassDefinition == null) { - RefinedObjectClassDefinition structuralObjectClassDefinition = getStructuralObjectClassDefinition(); - if (structuralObjectClassDefinition != null) { - compositeObjectClassDefinition = new CompositeRefinedObjectClassDefinitionImpl( - structuralObjectClassDefinition, getAuxiliaryObjectClassDefinitions()); - } - } - return compositeObjectClassDefinition; - } - - private void addAuxiliaryObjectClassNames(List auxiliaryObjectClassQNames, - PrismObject shadow) { - if (shadow == null) { - return; - } - for (QName aux: shadow.asObjectable().getAuxiliaryObjectClass()) { - if (!auxiliaryObjectClassQNames.contains(aux)) { - auxiliaryObjectClassQNames.add(aux); - } - } - } - - public RefinedAttributeDefinition findAttributeDefinition(QName attrName) throws SchemaException { - RefinedAttributeDefinition attrDef = getStructuralObjectClassDefinition().findAttributeDefinition(attrName); - if (attrDef != null) { - return attrDef; - } - for (RefinedObjectClassDefinition auxOcDef: getAuxiliaryObjectClassDefinitions()) { - attrDef = auxOcDef.findAttributeDefinition(attrName); - if (attrDef != null) { - return attrDef; - } - } - return null; - } - - public Collection getDependencies() { - if (dependencies == null) { - ResourceObjectTypeDefinitionType resourceAccountTypeDefinitionType = getResourceObjectTypeDefinitionType(); - if (resourceAccountTypeDefinitionType == null) { - // No dependencies. But we cannot set null as that means "unknown". So let's set empty collection instead. - dependencies = new ArrayList<>(); - } else { - dependencies = resourceAccountTypeDefinitionType.getDependency(); - } - } - return dependencies; - } - - public SecurityPolicyType getProjectionSecurityPolicy() { - return projectionSecurityPolicy; - } - - public void setProjectionSecurityPolicy(SecurityPolicyType projectionSecurityPolicy) { - this.projectionSecurityPolicy = projectionSecurityPolicy; - } - - public void setCanProject(boolean canProject) { - this.canProject = canProject; - } - - public boolean isCanProject() { - return canProject; - } - - public AssignmentPolicyEnforcementType getAssignmentPolicyEnforcementType() throws SchemaException { - // TODO: per-resource assignment enforcement - ResourceType resource = getResource(); - ProjectionPolicyType objectClassProjectionPolicy = determineObjectClassProjectionPolicy(); - - if (objectClassProjectionPolicy != null && objectClassProjectionPolicy.getAssignmentPolicyEnforcement() != null) { - return MiscSchemaUtil.getAssignmentPolicyEnforcementType(objectClassProjectionPolicy); - } - - ProjectionPolicyType globalAccountSynchronizationSettings = null; - if (resource != null){ - globalAccountSynchronizationSettings = resource.getProjection(); - } - - if (globalAccountSynchronizationSettings == null) { - globalAccountSynchronizationSettings = getLensContext().getAccountSynchronizationSettings(); - } - AssignmentPolicyEnforcementType globalAssignmentPolicyEnforcement = MiscSchemaUtil.getAssignmentPolicyEnforcementType(globalAccountSynchronizationSettings); - return globalAssignmentPolicyEnforcement; - } - - public boolean isLegalize() throws SchemaException { - ResourceType resource = getResource(); - - ProjectionPolicyType objectClassProjectionPolicy = determineObjectClassProjectionPolicy(); - if (objectClassProjectionPolicy != null) { - return BooleanUtils.isTrue(objectClassProjectionPolicy.isLegalize()); - } - ProjectionPolicyType globalAccountSynchronizationSettings = null; - if (resource != null){ - globalAccountSynchronizationSettings = resource.getProjection(); - } - - if (globalAccountSynchronizationSettings == null) { - globalAccountSynchronizationSettings = getLensContext().getAccountSynchronizationSettings(); - } - - if (globalAccountSynchronizationSettings == null){ - return false; - } - - return BooleanUtils.isTrue(globalAccountSynchronizationSettings.isLegalize()); - } - - private ProjectionPolicyType determineObjectClassProjectionPolicy() throws SchemaException { - RefinedResourceSchema refinedSchema = getRefinedResourceSchema(); - if (refinedSchema == null) { - return null; - } - - RefinedObjectClassDefinition objectClassDef = refinedSchema.getRefinedDefinition(resourceShadowDiscriminator.getKind(), - resourceShadowDiscriminator.getIntent()); - - if (objectClassDef == null) { - return null; - } - return objectClassDef.getProjection(); - } - - /** - * Recomputes the new state of account (accountNew). It is computed by applying deltas to the old state (accountOld). - * Assuming that oldAccount is already set (or is null if it does not exist) - */ - public void recompute() throws SchemaException { - ObjectDelta accDelta = getDelta(); - - PrismObject base = getObjectCurrent(); - if (base == null) { - base = getObjectOld(); - } - ObjectDelta syncDelta = getSyncDelta(); - if (base == null && syncDelta != null - && ChangeType.ADD.equals(syncDelta.getChangeType())) { - PrismObject objectToAdd = syncDelta.getObjectToAdd(); - if (objectToAdd != null) { - PrismObjectDefinition objectDefinition = objectToAdd.getDefinition(); - // TODO: remove constructor, use some factory method instead - base = getNotNullPrismContext().itemFactory().createObject(objectToAdd.getElementName(), objectDefinition); - base = syncDelta.computeChangedObject(base); - } - } - - if (accDelta == null) { - // No change - setObjectNew(base); - return; - } - - if (base == null && accDelta.isModify()) { - RefinedObjectClassDefinition rOCD = getCompositeObjectClassDefinition(); - if (rOCD != null) { - base = rOCD.createBlankShadow(); - } - } - - setObjectNew(accDelta.computeChangedObject(base)); - } - - public void clearIntermediateResults() { - //constructionDeltaSetTriple = null; - outboundConstruction = null; - squeezedAttributes = null; - } - - /** - * Returns delta suitable for execution. The primary and secondary deltas may not make complete sense all by themselves. - * E.g. they may both be MODIFY deltas even in case that the account should be created. The deltas begin to make sense - * only if combined with sync decision. This method provides the deltas all combined and ready for execution. - */ - @Override - public ObjectDelta getExecutableDelta() throws SchemaException { - SynchronizationPolicyDecision policyDecision = getSynchronizationPolicyDecision(); - ObjectDelta origDelta = getFixedDelta(); - if (policyDecision == SynchronizationPolicyDecision.ADD) { - // let's try to retrieve original (non-fixed) delta. Maybe it's ADD delta so we spare fixing it. - origDelta = getDelta(); - if (origDelta == null || origDelta.isModify()) { - // We need to convert modify delta to ADD - ObjectDelta addDelta = getPrismContext().deltaFactory().object().create(getObjectTypeClass(), - ChangeType.ADD); - RefinedObjectClassDefinition rObjectClassDef = getCompositeObjectClassDefinition(); - - if (rObjectClassDef == null) { - throw new IllegalStateException("Definition for account type " + getResourceShadowDiscriminator() - + " not found in the context, but it should be there"); - } - PrismObject newAccount = rObjectClassDef.createBlankShadow(); - addDelta.setObjectToAdd(newAccount); - - if (origDelta != null) { - addDelta.merge(origDelta); - } - return addDelta; - } - } else if (policyDecision == SynchronizationPolicyDecision.KEEP) { - // Any delta is OK - } else if (policyDecision == SynchronizationPolicyDecision.DELETE) { - ObjectDelta deleteDelta = getPrismContext().deltaFactory().object().create(getObjectTypeClass(), - ChangeType.DELETE); - String oid = getOid(); - if (oid == null) { - throw new IllegalStateException( - "Internal error: account context OID is null during attempt to create delete secondary delta; context=" - +this); - } - deleteDelta.setOid(oid); - return deleteDelta; - } else { - // This is either UNLINK or null, both are in fact the same as KEEP - // Any delta is OK - } - if (origDelta != null && origDelta.isImmutable()) { - // E.g. locked primary delta. - // We need modifiable delta for execution, e.g. to set metadata, oid and so on. - return origDelta.clone(); - } else { - return origDelta; - } - } - - public void checkConsistence() { - checkConsistence(null, true, false); - } - - @Override - public void checkConsistence(String contextDesc) { - super.checkConsistence(contextDesc); - if (secondaryDelta != null) { - boolean requireOid = isRequireSecondaryDeltaOid(); - // Secondary delta may not have OID yet (as it may relate to ADD primary delta that doesn't have OID yet) - checkConsistence(secondaryDelta, requireOid, getElementDesc() + " secondary delta in " + this + (contextDesc == null ? "" : " in " + contextDesc)); - } - } - - public void checkConsistence(String contextDesc, boolean fresh, boolean force) { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.IGNORE) { - // No not check these. they may be quite wild. - return; - } - super.checkConsistence(contextDesc); - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.BROKEN) { - return; - } - if (fresh && !force && resourceShadowDiscriminator != null && !resourceShadowDiscriminator.isTombstone()) { - if (resource == null) { - throw new IllegalStateException("Null resource in "+this + (contextDesc == null ? "" : " in " +contextDesc)); - } - if (resourceShadowDiscriminator == null) { - throw new IllegalStateException("Null resource account type in "+this + (contextDesc == null ? "" : " in " +contextDesc)); - } - } - if (syncDelta != null) { - try { - syncDelta.checkConsistence(true, true, true, ConsistencyCheckScope.THOROUGH); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException(e.getMessage()+"; in "+getElementDesc()+" sync delta in "+this + (contextDesc == null ? "" : " in " +contextDesc), e); - } catch (IllegalStateException e) { - throw new IllegalStateException(e.getMessage()+"; in "+getElementDesc()+" sync delta in "+this + (contextDesc == null ? "" : " in " +contextDesc), e); - } - } - } - - @Override - protected void checkConsistence(PrismObject object, String elementDesc, String contextDesc) { - super.checkConsistence(object, elementDesc, contextDesc); - ResourceAttributeContainer attributesContainer = ShadowUtil.getAttributesContainer(object); - if (attributesContainer != null) { - ResourceType resource = getResource(); - if (resource != null) { - String resourceNamespace = ResourceTypeUtil.getResourceNamespace(resource); - for(ResourceAttribute attribute: attributesContainer.getAttributes()) { - QName attrName = attribute.getElementName(); - if (SchemaConstants.NS_ICF_SCHEMA.equals(attrName.getNamespaceURI())) { - continue; - } - if (resourceNamespace.equals(attrName.getNamespaceURI())) { - continue; - } - String desc = elementDesc+" in "+this + (contextDesc == null ? "" : " in " +contextDesc); - throw new IllegalStateException("Invalid namespace for attribute "+attrName+" in "+desc); - } - } - } - } - - protected boolean isRequireSecondaryDeltaOid() { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.ADD || - synchronizationPolicyDecision == SynchronizationPolicyDecision.BROKEN || - synchronizationPolicyDecision == SynchronizationPolicyDecision.IGNORE) { - return false; - } - if (getResourceShadowDiscriminator() != null && getResourceShadowDiscriminator().getOrder() > 0) { - // These may not have the OID yet - return false; - } - return super.isRequireSecondaryDeltaOid(); - } - - @Override - public void cleanup() { - secondaryDelta = null; - resetSynchronizationPolicyDecision(); -// isLegal = null; -// isLegalOld = null; - isAssigned = false; - isAssignedOld = false; // ??? [med] - isActive = false; - } - - @Override - public void normalize() { - super.normalize(); - if (secondaryDelta != null) { - secondaryDelta.normalize(); - } - if (syncDelta != null) { - syncDelta.normalize(); - } - } - -// @Override -// public void reset() { -// super.reset(); -// wave = -1; -// fullShadow = false; -// isAssigned = false; -// isAssignedOld = false; -// isActive = false; -// resetSynchronizationPolicyDecision(); -// constructionDeltaSetTriple = null; -// outboundConstruction = null; -// dependencies = null; -// squeezedAttributes = null; -// accountPasswordPolicy = null; -// } - - protected void resetSynchronizationPolicyDecision() { - if (synchronizationPolicyDecision == SynchronizationPolicyDecision.DELETE || synchronizationPolicyDecision == SynchronizationPolicyDecision.UNLINK) { - toBeArchived = true; - } else if (synchronizationPolicyDecision != null) { - toBeArchived = false; - } - synchronizationPolicyDecision = null; - } - - @Override - public void adopt(PrismContext prismContext) throws SchemaException { - super.adopt(prismContext); - if (syncDelta != null) { - prismContext.adopt(syncDelta); - } - if (secondaryDelta != null) { - prismContext.adopt(secondaryDelta); - } - } - - @Override - public LensProjectionContext clone(LensContext lensContext) { - LensProjectionContext clone = new LensProjectionContext(lensContext, resourceShadowDiscriminator); - copyValues(clone, lensContext); - return clone; - } - - protected void copyValues(LensProjectionContext clone, LensContext lensContext) { - super.copyValues(clone, lensContext); - // do NOT clone transient values such as accountConstructionDeltaSetTriple - // these are not meant to be cloned and they are also not directly cloneable - clone.dependencies = this.dependencies; - clone.doReconciliation = this.doReconciliation; - clone.fullShadow = this.fullShadow; - clone.isAssigned = this.isAssigned; - clone.isAssignedOld = this.isAssignedOld; - clone.outboundConstruction = this.outboundConstruction; - clone.synchronizationPolicyDecision = this.synchronizationPolicyDecision; - clone.resource = this.resource; - clone.resourceShadowDiscriminator = this.resourceShadowDiscriminator; - clone.squeezedAttributes = cloneSqueezedAttributes(); - if (this.syncDelta != null) { - clone.syncDelta = this.syncDelta.clone(); - } - clone.secondaryDelta = cloneDelta(this.secondaryDelta); - clone.wave = this.wave; - clone.synchronizationSource = this.synchronizationSource; - } - - private Map,PrismPropertyDefinition>>> cloneSqueezedAttributes() { - if (squeezedAttributes == null) { - return null; - } - Map,PrismPropertyDefinition>>> clonedMap = new HashMap<>(); - for (Entry,PrismPropertyDefinition>>> entry: squeezedAttributes.entrySet()) { - clonedMap.put(entry.getKey(), entry.getValue().clone(ItemValueWithOrigin::clone)); - } - return clonedMap; - } - - /** - * Returns true if the projection has any value for specified attribute. - */ - public boolean hasValueForAttribute(QName attributeName) { - ItemPath attrPath = ItemPath.create(ShadowType.F_ATTRIBUTES, attributeName); - if (getObjectNew() != null) { - PrismProperty attrNew = getObjectNew().findProperty(attrPath); - if (attrNew != null && !attrNew.isEmpty()) { - return true; - } - } - return false; - } - - private boolean hasValueForAttribute(QName attributeName, Collection> acPpvSet) { - if (acPpvSet == null) { - return false; - } - for (PrismPropertyValue acPpv: acPpvSet) { - Construction ac = acPpv.getValue(); - if (ac.hasValueForAttribute(attributeName)) { - return true; - } - } - return false; - } - - @Override - public void checkEncrypted() { - super.checkEncrypted(); - if (syncDelta != null) { - CryptoUtil.checkEncrypted(syncDelta); - } - if (secondaryDelta != null) { - CryptoUtil.checkEncrypted(secondaryDelta); - } - } - - @Override - public String getHumanReadableName() { - if (humanReadableName == null) { - StringBuilder sb = new StringBuilder(); - sb.append("account("); - String humanReadableAccountIdentifier = getHumanReadableIdentifier(); - if (StringUtils.isEmpty(humanReadableAccountIdentifier)) { - sb.append("no ID"); - } else { - sb.append("ID "); - sb.append(humanReadableAccountIdentifier); - } - ResourceShadowDiscriminator discr = getResourceShadowDiscriminator(); - if (discr != null) { - sb.append(", type '"); - sb.append(discr.getIntent()); - sb.append("', "); - if (discr.getOrder() != 0) { - sb.append("order ").append(discr.getOrder()).append(", "); - } - } else { - sb.append(" (no discriminator) "); - } - sb.append(getResource()); - sb.append(")"); - humanReadableName = sb.toString(); - } - return humanReadableName; - } - - private String getHumanReadableIdentifier() { - PrismObject object = getObjectNew(); - if (object == null) { - object = getObjectOld(); - } - if (object == null) { - object = getObjectCurrent(); - } - if (object == null) { - return null; - } - if (object.canRepresent(ShadowType.class)) { - PrismObject shadow = (PrismObject)object; - Collection> identifiers = ShadowUtil.getPrimaryIdentifiers(shadow); - if (identifiers == null) { - return null; - } - StringBuilder sb = new StringBuilder(); - Iterator> iterator = identifiers.iterator(); - while (iterator.hasNext()) { - ResourceAttribute id = iterator.next(); - sb.append(id.toHumanReadableString()); - if (iterator.hasNext()) { - sb.append(","); - } - } - return sb.toString(); - } else { - return object.toString(); - } - } - - @Override - public String debugDump(int indent) { - return debugDump(indent, true); - } - - public String debugDump(int indent, boolean showTriples) { - StringBuilder sb = new StringBuilder(); - SchemaDebugUtil.indentDebugDump(sb, indent); - sb.append("PROJECTION "); - sb.append(getObjectTypeClass() == null ? "null" : getObjectTypeClass().getSimpleName()); - sb.append(" "); - sb.append(getResourceShadowDiscriminator()); - if (resource != null) { - sb.append(" : "); - sb.append(resource.getName().getOrig()); - } - sb.append("\n"); - SchemaDebugUtil.indentDebugDump(sb, indent + 1); - sb.append("OID: ").append(getOid()); - sb.append(", wave ").append(wave); - if (fullShadow) { - sb.append(", full"); - } else { - sb.append(", shadow"); - } - sb.append(", exists=").append(isExists); - if (!shadowExistsInRepo) { - sb.append(" (shadow not in repo)"); - } - sb.append(", assigned=").append(isAssignedOld).append("->").append(isAssigned); - sb.append(", active=").append(isActive); - sb.append(", legal=").append(isLegalOld).append("->").append(isLegal); - sb.append(", recon=").append(doReconciliation); - sb.append(", canProject=").append(canProject); - sb.append(", syncIntent=").append(getSynchronizationIntent()); - sb.append(", decision=").append(synchronizationPolicyDecision); - if (!isFresh()) { - sb.append(", NOT FRESH"); - } - if (resourceShadowDiscriminator != null && resourceShadowDiscriminator.isTombstone()) { - sb.append(", TOMBSTONE"); - } - if (syncAbsoluteTrigger) { - sb.append(", SYNC TRIGGER"); - } - if (getIteration() != 0) { - sb.append(", iteration=").append(getIteration()).append(" (").append(getIterationToken()).append(")"); - } - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("old"), getObjectOld(), indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("current"), getObjectCurrent(), indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("new"), getObjectNew(), indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("primary delta"), getPrimaryDelta(), indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("secondary delta"), getSecondaryDelta(), indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("sync delta"), getSyncDelta(), indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("executed deltas"), getExecutedDeltas(), indent+1); - - if (showTriples) { - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("constructionDeltaSetTriple"), constructionDeltaSetTriple, indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("outbound account construction"), outboundConstruction, indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("squeezed attributes"), squeezedAttributes, indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("squeezed associations"), squeezedAssociations, indent + 1); - - sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("squeezed auxiliary object classes"), squeezedAuxiliaryObjectClasses, indent + 1); - - // This is just a debug thing -// sb.append("\n"); -// DebugUtil.indentDebugDump(sb, indent); -// sb.append("ACCOUNT dependencies\n"); -// sb.append(DebugUtil.debugDump(dependencies, indent + 1)); - } - - return sb.toString(); - } - - @Override - protected String getElementDefaultDesc() { - return "projection"; - } - - @Override - public String toString() { - return "LensProjectionContext(" + (getObjectTypeClass() == null ? "null" : getObjectTypeClass().getSimpleName()) + ":" + getOid() + - ( resource == null ? "" : " on " + resource ) + ")"; - } - - /** - * Return a human readable name of the projection object suitable for logs. - */ - public String toHumanReadableString() { - if (humanReadableString == null) { - if (resourceShadowDiscriminator == null) { - humanReadableString = "(null" + resource + ")"; - } else if (resource != null) { - humanReadableString = "("+getKindValue(resourceShadowDiscriminator.getKind()) + " ("+resourceShadowDiscriminator.getIntent()+") on " + resource + ")"; - } else { - humanReadableString = "("+getKindValue(resourceShadowDiscriminator.getKind()) + " ("+resourceShadowDiscriminator.getIntent()+") on " + resourceShadowDiscriminator.getResourceOid() + ")"; - } - } - return humanReadableString; - } - - public String getHumanReadableKind() { - if (resourceShadowDiscriminator == null) { - return "resource object"; - } - return getKindValue(resourceShadowDiscriminator.getKind()); - } - - private String getKindValue(ShadowKindType kind) { - if (kind == null) { - return "null"; - } - return kind.value(); - } - - @Override - protected String getElementDesc() { - if (resourceShadowDiscriminator == null) { - return "shadow"; - } - return getKindValue(resourceShadowDiscriminator.getKind()); - } - - void addToPrismContainer(PrismContainer lensProjectionContextTypeContainer, LensContext.ExportType exportType) throws SchemaException { - LensProjectionContextType lensProjectionContextType = lensProjectionContextTypeContainer.createNewValue().asContainerable(); - super.storeIntoLensElementContextType(lensProjectionContextType, exportType); - lensProjectionContextType.setWave(wave); - lensProjectionContextType.setResourceShadowDiscriminator(resourceShadowDiscriminator != null ? - resourceShadowDiscriminator.toResourceShadowDiscriminatorType() : null); - lensProjectionContextType.setFullShadow(fullShadow); - lensProjectionContextType.setIsExists(isExists); - lensProjectionContextType.setSynchronizationPolicyDecision(synchronizationPolicyDecision != null ? synchronizationPolicyDecision.toSynchronizationPolicyDecisionType() : null); - lensProjectionContextType.setDoReconciliation(doReconciliation); - lensProjectionContextType.setSynchronizationSituationDetected(synchronizationSituationDetected); - lensProjectionContextType.setSynchronizationSituationResolved(synchronizationSituationResolved); - if (exportType != LensContext.ExportType.MINIMAL) { - lensProjectionContextType.setSyncDelta(syncDelta != null ? DeltaConvertor.toObjectDeltaType(syncDelta) : null); - lensProjectionContextType - .setSecondaryDelta(secondaryDelta != null ? DeltaConvertor.toObjectDeltaType(secondaryDelta) : null); - lensProjectionContextType.setIsAssigned(isAssigned); - lensProjectionContextType.setIsAssignedOld(isAssignedOld); - lensProjectionContextType.setIsActive(isActive); - lensProjectionContextType.setIsLegal(isLegal); - lensProjectionContextType.setIsLegalOld(isLegalOld); - if (exportType != LensContext.ExportType.REDUCED && projectionSecurityPolicy != null) { - ObjectReferenceType secRef = new ObjectReferenceType(); - secRef.asReferenceValue().setObject(projectionSecurityPolicy.asPrismObject()); - lensProjectionContextType.setProjectionSecurityPolicyRef(secRef); - } - lensProjectionContextType.setSyncAbsoluteTrigger(syncAbsoluteTrigger); - } - } - - public static LensProjectionContext fromLensProjectionContextType(LensProjectionContextType projectionContextType, LensContext lensContext, Task task, OperationResult result) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, ExpressionEvaluationException { - - String objectTypeClassString = projectionContextType.getObjectTypeClass(); - if (StringUtils.isEmpty(objectTypeClassString)) { - throw new SystemException("Object type class is undefined in LensProjectionContextType"); - } - ResourceShadowDiscriminator resourceShadowDiscriminator = ResourceShadowDiscriminator.fromResourceShadowDiscriminatorType( - projectionContextType.getResourceShadowDiscriminator(), false); - - LensProjectionContext projectionContext = new LensProjectionContext(lensContext, resourceShadowDiscriminator); - - projectionContext.retrieveFromLensElementContextType(projectionContextType, task, result); - if (projectionContextType.getSyncDelta() != null) { - projectionContext.syncDelta = DeltaConvertor.createObjectDelta(projectionContextType.getSyncDelta(), lensContext.getPrismContext()); - } else { - projectionContext.syncDelta = null; - } - ObjectDeltaType secondaryDeltaType = projectionContextType.getSecondaryDelta(); - projectionContext.secondaryDelta = secondaryDeltaType != null ? - DeltaConvertor.createObjectDelta(secondaryDeltaType, lensContext.getPrismContext()) : null; - ObjectType object = projectionContextType.getObjectNew() != null ? projectionContextType.getObjectNew() : projectionContextType.getObjectOld(); - projectionContext.fixProvisioningTypeInDelta(projectionContext.secondaryDelta, object, task, result); - - projectionContext.wave = projectionContextType.getWave() != null ? projectionContextType.getWave() : 0; - projectionContext.fullShadow = projectionContextType.isFullShadow() != null ? projectionContextType.isFullShadow() : false; - projectionContext.isAssigned = projectionContextType.isIsAssigned() != null ? projectionContextType.isIsAssigned() : false; - projectionContext.isAssignedOld = projectionContextType.isIsAssignedOld() != null ? projectionContextType.isIsAssignedOld() : false; - projectionContext.isActive = projectionContextType.isIsActive() != null ? projectionContextType.isIsActive() : false; - projectionContext.isLegal = projectionContextType.isIsLegal(); - projectionContext.isLegalOld = projectionContextType.isIsLegalOld(); - projectionContext.isExists = projectionContextType.isIsExists() != null ? projectionContextType.isIsExists() : false; - projectionContext.synchronizationPolicyDecision = SynchronizationPolicyDecision.fromSynchronizationPolicyDecisionType(projectionContextType.getSynchronizationPolicyDecision()); - projectionContext.doReconciliation = projectionContextType.isDoReconciliation() != null ? projectionContextType.isDoReconciliation() : false; - projectionContext.synchronizationSituationDetected = projectionContextType.getSynchronizationSituationDetected(); - projectionContext.synchronizationSituationResolved = projectionContextType.getSynchronizationSituationResolved(); - ObjectReferenceType projectionSecurityPolicyRef = projectionContextType.getProjectionSecurityPolicyRef(); - if (projectionSecurityPolicyRef != null) { - projectionContext.projectionSecurityPolicy = (SecurityPolicyType) projectionSecurityPolicyRef.getObjectable(); - } - projectionContext.syncAbsoluteTrigger = projectionContextType.isSyncAbsoluteTrigger(); - - return projectionContext; - } - - // determines whether full shadow is present, based on operation result got from provisioning - public void determineFullShadowFlag(PrismObject loadedShadow) { - ShadowType shadowType = loadedShadow.asObjectable(); - if (ShadowUtil.isDead(shadowType) || !ShadowUtil.isExists(shadowType)) { - setFullShadow(false); - return; - } - OperationResultType fetchResult = shadowType.getFetchResult(); - if (fetchResult != null - && (fetchResult.getStatus() == OperationResultStatusType.PARTIAL_ERROR - || fetchResult.getStatus() == OperationResultStatusType.FATAL_ERROR)) { // todo what about other kinds of status? [e.g. in-progress] - setFullShadow(false); - } else { - setFullShadow(true); - } - } - - public boolean isToBeArchived() { - return toBeArchived; - } - - public void setToBeArchived(boolean toBeArchived) { - this.toBeArchived = toBeArchived; - } - - public String getResourceOid() { - if (resource != null) { - return resource.getOid(); - } else if (resourceShadowDiscriminator != null) { - return resourceShadowDiscriminator.getResourceOid(); - } else { - return null; - } - } - - public ResourceObjectVolatilityType getVolatility() throws SchemaException { - RefinedObjectClassDefinition structuralObjectClassDefinition = getStructuralObjectClassDefinition(); - if (structuralObjectClassDefinition == null) { - return null; - } - return structuralObjectClassDefinition.getVolatility(); - } - - public boolean hasPendingOperations() { - PrismObject current = getObjectCurrent(); - if (current == null) { - return false; - } - return !current.asObjectable().getPendingOperation().isEmpty(); - } - - @Override - public void forEachDelta(Consumer> consumer) { - super.forEachDelta(consumer); - if (secondaryDelta != null) { - consumer.accept(secondaryDelta); - } - } - - PolyString resolveNameIfKnown(Class objectClass, String oid) { - if (ResourceType.class.equals(objectClass)) { - if (resource != null && oid.equals(resource.getOid())) { - return PolyString.toPolyString(resource.getName()); - } - } else if (ShadowType.class.equals(objectClass)) { - PrismObject object = getObjectAny(); - if (object != null && oid.equals(object.getOid())) { - if (object.getName() != null) { - return object.getName(); - } else { - try { - return ShadowUtil.determineShadowName(object); - } catch (SchemaException e) { - LoggingUtils.logUnexpectedException(LOGGER, "Couldn't determine shadow name for {}", e, object); - return null; - } - } - } - } - return null; - } - - public String getResourceName() { - ResourceType resource = getResource(); - return resource != null ? PolyString.getOrig(resource.getName()) : getResourceOid(); - } - - public boolean isSynchronizationSource() { - return synchronizationSource; - } - - public void setSynchronizationSource(boolean synchronizationSource) { - this.synchronizationSource = synchronizationSource; - } -} +/* + * Copyright (c) 2010-2019 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; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.function.Consumer; + +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.common.refinery.*; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.polystring.PolyString; +import com.evolveum.midpoint.schema.DeltaConvertor; +import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.LoggingUtils; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.BooleanUtils; +import org.jvnet.jaxb2_commons.lang.Validate; + +import com.evolveum.midpoint.common.crypto.CryptoUtil; +import com.evolveum.midpoint.model.api.context.ModelProjectionContext; +import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; +import com.evolveum.midpoint.prism.delta.ChangeType; +import com.evolveum.midpoint.prism.delta.DeltaSetTriple; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.prism.util.ObjectDeltaObject; +import com.evolveum.midpoint.schema.processor.ResourceAttribute; +import com.evolveum.midpoint.schema.processor.ResourceAttributeContainer; +import com.evolveum.midpoint.schema.processor.ResourceSchema; +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.schema.util.ResourceTypeUtil; +import com.evolveum.midpoint.schema.util.SchemaDebugUtil; +import com.evolveum.midpoint.util.DebugUtil; + +/** + * @author semancik + * + */ +public class LensProjectionContext extends LensElementContext implements ModelProjectionContext { + + private static final Trace LOGGER = TraceManager.getTrace(LensProjectionContext.class); + + private ObjectDelta syncDelta; + + /** + * Is this projection the source of the synchronization? (The syncDelta attribute could be used for this but in + * reality it is not always present.) We need this information e.g. when it's not possible to record a clockwork + * exception to focus (e.g. as in MID-5801). The alternate way is to record it into shadow representing the synchronization + * source, e.g. the object being imported, reconciled, or live-synced. + */ + private boolean synchronizationSource; + + private ObjectDelta secondaryDelta; + + /** + * If set to true: absolute state of this projection was detected by the synchronization. + * This is mostly for debugging and visibility. It is not used by projection logic. + */ + private boolean syncAbsoluteTrigger = false; + + /** + * The wave in which this resource should be processed. Initial value of -1 means "undetermined". + */ + private int wave = -1; + + /** + * Indicates that the wave computation is still in progress. + */ + private transient boolean waveIncomplete = false; + + /** + * Definition of account type. + */ + private ResourceShadowDiscriminator resourceShadowDiscriminator; + + private boolean fullShadow = false; + + /** + * True if the account is assigned to the user by a valid assignment. It may be false for accounts that are either + * found to be illegal by live sync, were unassigned from user, etc. + * If set to null the situation is not yet known. Null is a typical value when the context is constructed. + */ + private boolean isAssigned; + private boolean isAssignedOld; + + /** + * True if the account should be part of the synchronization. E.g. outbound expression should be applied to it. + */ + private boolean isActive; + + /** + * True if there is a valid assignment for this projection and/or the policy allows such projection to exist. + */ + private Boolean isLegal = null; + private Boolean isLegalOld = null; + + /** + * True if the projection exists (or will exist) on resource. False if it does not exist. + * NOTE: entire projection is loaded with pointInTime=future. Therefore this does NOT + * reflect actual situation. If there is a pending operation to create the object then + * isExists will in fact be true. + */ + private boolean isExists; + + /** + * True if shadow exists in the repo. It is set to false after projector discovers that a shadow is gone. + * This is a corner case, but it may happen: if shadow is unintentionally deleted, if the shadow is + * cleaned up by another thread and so on. + */ + private transient boolean shadowExistsInRepo = true; + + /** + * Decision regarding the account. It indicated what the engine has DECIDED TO DO with the context. + * If set to null no decision was made yet. Null is also a typical value when the context is created. + */ + private SynchronizationPolicyDecision synchronizationPolicyDecision; + + /** + * True if we want to reconcile account in this context. + */ + private boolean doReconciliation; + + /** + * false if the context should be not taken into the account while synchronizing changes from other resource + */ + private boolean canProject = true; + + /** + * Synchronization situation as it was originally detected by the synchronization code (SynchronizationService). + * This is mostly for debug purposes. Projector and clockwork do not need to care about this. + * The synchronization intent is used instead. + */ + private SynchronizationSituationType synchronizationSituationDetected = null; + /** + * Synchronization situation which was the result of synchronization reaction (projector and clockwork run). + * This is mostly for debug purposes. Projector and clockwork do not care about this (except for setting it). + * The synchronization decision is used instead. + */ + private SynchronizationSituationType synchronizationSituationResolved = null; + + /** + * Delta set triple for accounts. Specifies which accounts should be added, removed or stay as they are. + * It tells almost nothing about attributes directly although the information about attributes are inside + * each account construction (in a form of ValueConstruction that contains attribute delta triples). + * + * Intermediary computation result. It is stored to allow re-computing of account constructions during + * iterative computations. + * + * Source: AssignmentProcessor + * Target: ConsolidationProcessor / ReconciliationProcessor (via squeezed structures) + */ + private transient PrismValueDeltaSetTriple> constructionDeltaSetTriple; + + /** + * Triples for outbound mappings; similar to the above. + * Source: OutboundProcessor + * Target: ConsolidationProcessor / ReconciliationProcessor (via squeezed structures) + */ + private transient Construction outboundConstruction; + + /** + * Postprocessed triples from the above two properties. + * Source: ConsolidationProcessor + * Target: ReconciliationProcessor + */ + private transient Map,PrismPropertyDefinition>>> squeezedAttributes; + private transient Map,PrismContainerDefinition>>> squeezedAssociations; + private transient Map,PrismPropertyDefinition>>> squeezedAuxiliaryObjectClasses; + + private transient Collection dependencies = null; + + // Cached copy, to avoid constructing it over and over again + private transient PrismObjectDefinition shadowDefinition = null; + + private transient RefinedObjectClassDefinition structuralObjectClassDefinition; + private transient Collection auxiliaryObjectClassDefinitions; + private transient CompositeRefinedObjectClassDefinition compositeObjectClassDefinition; + + private SecurityPolicyType projectionSecurityPolicy; + + /** + * Resource that hosts this projection. + */ + transient private ResourceType resource; + + /** + * EXPERIMENTAL. A flag that this projection context has to be put into 'history archive'. + * Necessary to evaluate old state of hasLinkedAccount. + * + * TODO implement as non-transient. + */ + transient private boolean toBeArchived; + + transient private String humanReadableName; + + private Map> entitlementMap = new HashMap<>(); + + transient private String humanReadableString; + + LensProjectionContext(LensContext lensContext, ResourceShadowDiscriminator resourceAccountType) { + super(ShadowType.class, lensContext); + this.resourceShadowDiscriminator = resourceAccountType; + this.isAssigned = false; + this.isAssignedOld = false; + } + + public ObjectDelta getSyncDelta() { + return syncDelta; + } + + public void setSyncDelta(ObjectDelta syncDelta) { + this.syncDelta = syncDelta; + } + + @Override + public ObjectDelta getSecondaryDelta() { + return secondaryDelta; + } + + @Override + public Collection> getAllDeltas() { + List> deltas = new ArrayList<>(2); + ObjectDelta primaryDelta = getPrimaryDelta(); + if (primaryDelta != null) { + deltas.add(primaryDelta); + } + if (secondaryDelta != null) { + deltas.add(secondaryDelta); + } + return deltas; + } + + @Override + public ObjectDeltaObject getObjectDeltaObject() throws SchemaException { + return new ObjectDeltaObject<>(getObjectCurrent(), getDelta(), getObjectNew(), getObjectDefinition()); + } + + public boolean hasSecondaryDelta() { + return secondaryDelta != null && !secondaryDelta.isEmpty(); + } + + @Override + public void setSecondaryDelta(ObjectDelta secondaryDelta) { + this.secondaryDelta = secondaryDelta; + } + + public void addSecondaryDelta(ObjectDelta delta) throws SchemaException { + if (secondaryDelta == null) { + secondaryDelta = delta; + } else { + secondaryDelta.merge(delta); + } + } + + @Override + public void swallowToSecondaryDelta(ItemDelta itemDelta) throws SchemaException { + if (secondaryDelta == null) { + secondaryDelta = getPrismContext().deltaFactory().object().create(getObjectTypeClass(), ChangeType.MODIFY); + secondaryDelta.setOid(getOid()); + } + LensUtil.setDeltaOldValue(this, itemDelta); + secondaryDelta.swallow(itemDelta); + } + + @Override + public void deleteSecondaryDeltas() { + secondaryDelta = null; + } + + @Override + public void setOid(String oid) { + super.setOid(oid); + if (secondaryDelta != null) { + secondaryDelta.setOid(oid); + } + } + + public boolean isSyncAbsoluteTrigger() { + return syncAbsoluteTrigger; + } + + public void setSyncAbsoluteTrigger(boolean syncAbsoluteTrigger) { + this.syncAbsoluteTrigger = syncAbsoluteTrigger; + } + + public int getWave() { + return wave; + } + + public void setWave(int wave) { + this.wave = wave; + } + + public boolean isWaveIncomplete() { + return waveIncomplete; + } + + public void setWaveIncomplete(boolean waveIncomplete) { + this.waveIncomplete = waveIncomplete; + } + + public boolean isDoReconciliation() { + return doReconciliation; + } + + public void setDoReconciliation(boolean doReconciliation) { + this.doReconciliation = doReconciliation; + } + + @Override + public ResourceShadowDiscriminator getResourceShadowDiscriminator() { + return resourceShadowDiscriminator; + } + + public void markTombstone() { + if (resourceShadowDiscriminator != null) { + resourceShadowDiscriminator.setTombstone(true); + } + setExists(false); + setFullShadow(false); + humanReadableName = null; + } + + public void setResourceShadowDiscriminator(ResourceShadowDiscriminator resourceShadowDiscriminator) { + this.resourceShadowDiscriminator = resourceShadowDiscriminator; + } + + public boolean compareResourceShadowDiscriminator(ResourceShadowDiscriminator rsd, boolean compareOrder) { + Validate.notNull(rsd.getResourceOid()); + if (resourceShadowDiscriminator == null) { + // This may be valid case e.g. in case of broken contexts or if a context is just loading + return false; + } + if (!rsd.getResourceOid().equals(resourceShadowDiscriminator.getResourceOid())) { + return false; + } + if (!rsd.getKind().equals(resourceShadowDiscriminator.getKind())) { + return false; + } + if (rsd.isTombstone() != resourceShadowDiscriminator.isTombstone()) { + return false; + } + if (rsd.getIntent() == null) { + try { + if (!getStructuralObjectClassDefinition().isDefaultInAKind()) { + return false; + } + } catch (SchemaException e) { + throw new SystemException("Internal error: "+e.getMessage(), e); + } + } else if (!rsd.getIntent().equals(resourceShadowDiscriminator.getIntent())) { + return false; + } + if (!Objects.equals(rsd.getTag(), resourceShadowDiscriminator.getTag())) { + return false; + } + + if (compareOrder && rsd.getOrder() != resourceShadowDiscriminator.getOrder()) { + return false; + } + + return true; + } + + public boolean isTombstone() { + if (resourceShadowDiscriminator == null) { + return false; + } + return resourceShadowDiscriminator.isTombstone(); + } + + public void addAccountSyncDelta(ObjectDelta delta) throws SchemaException { + if (syncDelta == null) { + syncDelta = delta; + } else { + syncDelta.merge(delta); + } + } + + public boolean isAdd() { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.ADD) { + return true; + } else if (synchronizationPolicyDecision != null) { + return false; + } else { + return ObjectDelta.isAdd(getPrimaryDelta()) || ObjectDelta.isAdd(getSecondaryDelta()); + } + } + + public boolean isModify() { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.KEEP) { + return true; + } else if (synchronizationPolicyDecision != null) { + return false; + } else { + return super.isModify(); + } + } + + public boolean isDelete() { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.DELETE) { + return true; + } else if (synchronizationPolicyDecision != null) { + return false; + } else { + return ObjectDelta.isDelete(syncDelta) || ObjectDelta.isDelete(getPrimaryDelta()) || ObjectDelta.isDelete(getSecondaryDelta()); + } + } + + @Override + public ArchetypeType getArchetype() { + throw new UnsupportedOperationException("Archetypes are not supported for projections."); + } + + public ResourceType getResource() { + return resource; + } + + public void setResource(ResourceType resource) { + this.resource = resource; + } + + public Map> getEntitlementMap() { + return entitlementMap; + } + + public void setEntitlementMap(Map> entitlementMap) { + this.entitlementMap = entitlementMap; + } + + @Override + public PrismObjectDefinition getObjectDefinition() { + if (shadowDefinition == null) { + try { + shadowDefinition = ShadowUtil.applyObjectClass(super.getObjectDefinition(), getCompositeObjectClassDefinition()); + } catch (SchemaException e) { + // This should not happen + throw new SystemException(e.getMessage(), e); + } + } + return shadowDefinition; + } + + public boolean isAssigned() { + return isAssigned; + } + + public void setAssigned(boolean isAssigned) { + this.isAssigned = isAssigned; + } + + public boolean isAssignedOld() { + return isAssignedOld; + } + + public void setAssignedOld(boolean isAssignedOld) { + this.isAssignedOld = isAssignedOld; + } + + public boolean isActive() { + return isActive; + } + + public void setActive(boolean isActive) { + this.isActive = isActive; + } + + public Boolean isLegal() { + return isLegal; + } + + public void setLegal(Boolean isLegal) { + this.isLegal = isLegal; + } + + public Boolean isLegalOld() { + return isLegalOld; + } + + public void setLegalOld(Boolean isLegalOld) { + this.isLegalOld = isLegalOld; + } + + public boolean isExists() { + return isExists; + } + + public void setExists(boolean exists) { + this.isExists = exists; + } + + public boolean isShadowExistsInRepo() { + return shadowExistsInRepo; + } + + public void setShadowExistsInRepo(boolean shadowExistsInRepo) { + this.shadowExistsInRepo = shadowExistsInRepo; + } + + public SynchronizationPolicyDecision getSynchronizationPolicyDecision() { + return synchronizationPolicyDecision; + } + + public void setSynchronizationPolicyDecision(SynchronizationPolicyDecision policyDecision) { + this.synchronizationPolicyDecision = policyDecision; + } + + public SynchronizationSituationType getSynchronizationSituationDetected() { + return synchronizationSituationDetected; + } + + public void setSynchronizationSituationDetected( + SynchronizationSituationType synchronizationSituationDetected) { + this.synchronizationSituationDetected = synchronizationSituationDetected; + } + + public SynchronizationSituationType getSynchronizationSituationResolved() { + return synchronizationSituationResolved; + } + + void setSynchronizationSituationResolved(SynchronizationSituationType synchronizationSituationResolved) { + this.synchronizationSituationResolved = synchronizationSituationResolved; + } + + public boolean isFullShadow() { + return fullShadow; + } + + /** + * Returns true if full shadow is available, either loaded or in a create delta. + */ + public boolean hasFullShadow() { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.ADD) { + return true; + } + return isFullShadow(); + } + + public void setFullShadow(boolean fullShadow) { + this.fullShadow = fullShadow; + } + + public ShadowKindType getKind() { + ResourceShadowDiscriminator discr = getResourceShadowDiscriminator(); + if (discr != null) { + return discr.getKind(); + } + if (getObjectOld()!=null) { + return getObjectOld().asObjectable().getKind(); + } + if (getObjectCurrent()!=null) { + return getObjectCurrent().asObjectable().getKind(); + } + if (getObjectNew()!=null) { + return getObjectNew().asObjectable().getKind(); + } + return ShadowKindType.ACCOUNT; + } + + public PrismValueDeltaSetTriple> getConstructionDeltaSetTriple() { + return constructionDeltaSetTriple; + } + + public void setConstructionDeltaSetTriple( + PrismValueDeltaSetTriple> constructionDeltaSetTriple) { + this.constructionDeltaSetTriple = constructionDeltaSetTriple; + } + + public Construction getOutboundConstruction() { + return outboundConstruction; + } + + public void setOutboundConstruction(Construction outboundConstruction) { + this.outboundConstruction = outboundConstruction; + } + + public Map,PrismPropertyDefinition>>> getSqueezedAttributes() { + return squeezedAttributes; + } + + public void setSqueezedAttributes(Map,PrismPropertyDefinition>>> squeezedAttributes) { + this.squeezedAttributes = squeezedAttributes; + } + + public Map,PrismContainerDefinition>>> getSqueezedAssociations() { + return squeezedAssociations; + } + + public void setSqueezedAssociations( + Map,PrismContainerDefinition>>> squeezedAssociations) { + this.squeezedAssociations = squeezedAssociations; + } + + public Map, PrismPropertyDefinition>>> getSqueezedAuxiliaryObjectClasses() { + return squeezedAuxiliaryObjectClasses; + } + + public void setSqueezedAuxiliaryObjectClasses( + Map, PrismPropertyDefinition>>> squeezedAuxiliaryObjectClasses) { + this.squeezedAuxiliaryObjectClasses = squeezedAuxiliaryObjectClasses; + } + + public ResourceObjectTypeDefinitionType getResourceObjectTypeDefinitionType() { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.BROKEN) { + return null; + } + ResourceShadowDiscriminator discr = getResourceShadowDiscriminator(); + if (discr == null) { + return null; // maybe when an account is deleted + } + if (resource == null) { + return null; + } + ResourceObjectTypeDefinitionType def = ResourceTypeUtil.getResourceObjectTypeDefinitionType(resource, discr.getKind(), discr.getIntent()); + return def; + } + + private ResourceSchema getResourceSchema() throws SchemaException { + return RefinedResourceSchemaImpl.getResourceSchema(resource, getNotNullPrismContext()); + } + + public RefinedResourceSchema getRefinedResourceSchema() throws SchemaException { + if (resource == null) { + return null; + } + return RefinedResourceSchemaImpl.getRefinedSchema(resource, LayerType.MODEL, getNotNullPrismContext()); + } + + public RefinedObjectClassDefinition getStructuralObjectClassDefinition() throws SchemaException { + if (structuralObjectClassDefinition == null) { + RefinedResourceSchema refinedSchema = getRefinedResourceSchema(); + if (refinedSchema == null) { + return null; + } + structuralObjectClassDefinition = refinedSchema.getRefinedDefinition(getResourceShadowDiscriminator().getKind(), getResourceShadowDiscriminator().getIntent()); + } + return structuralObjectClassDefinition; + } + + public Collection getAuxiliaryObjectClassDefinitions() throws SchemaException { + if (auxiliaryObjectClassDefinitions == null) { + refreshAuxiliaryObjectClassDefinitions(); + } + return auxiliaryObjectClassDefinitions; + } + + public void refreshAuxiliaryObjectClassDefinitions() throws SchemaException { + RefinedResourceSchema refinedSchema = getRefinedResourceSchema(); + if (refinedSchema == null) { + return; + } + List auxiliaryObjectClassQNames = new ArrayList<>(); + addAuxiliaryObjectClassNames(auxiliaryObjectClassQNames, getObjectOld()); + addAuxiliaryObjectClassNames(auxiliaryObjectClassQNames, getObjectNew()); + auxiliaryObjectClassDefinitions = new ArrayList<>(auxiliaryObjectClassQNames.size()); + for (QName auxiliaryObjectClassQName: auxiliaryObjectClassQNames) { + RefinedObjectClassDefinition auxiliaryObjectClassDef = refinedSchema.getRefinedDefinition(auxiliaryObjectClassQName); + if (auxiliaryObjectClassDef == null) { + throw new SchemaException("Auxiliary object class "+auxiliaryObjectClassQName+" specified in "+this+" does not exist"); + } + auxiliaryObjectClassDefinitions.add(auxiliaryObjectClassDef); + } + compositeObjectClassDefinition = null; + } + + public CompositeRefinedObjectClassDefinition getCompositeObjectClassDefinition() throws SchemaException { + if (compositeObjectClassDefinition == null) { + RefinedObjectClassDefinition structuralObjectClassDefinition = getStructuralObjectClassDefinition(); + if (structuralObjectClassDefinition != null) { + compositeObjectClassDefinition = new CompositeRefinedObjectClassDefinitionImpl( + structuralObjectClassDefinition, getAuxiliaryObjectClassDefinitions()); + } + } + return compositeObjectClassDefinition; + } + + private void addAuxiliaryObjectClassNames(List auxiliaryObjectClassQNames, + PrismObject shadow) { + if (shadow == null) { + return; + } + for (QName aux: shadow.asObjectable().getAuxiliaryObjectClass()) { + if (!auxiliaryObjectClassQNames.contains(aux)) { + auxiliaryObjectClassQNames.add(aux); + } + } + } + + public RefinedAttributeDefinition findAttributeDefinition(QName attrName) throws SchemaException { + RefinedAttributeDefinition attrDef = getStructuralObjectClassDefinition().findAttributeDefinition(attrName); + if (attrDef != null) { + return attrDef; + } + for (RefinedObjectClassDefinition auxOcDef: getAuxiliaryObjectClassDefinitions()) { + attrDef = auxOcDef.findAttributeDefinition(attrName); + if (attrDef != null) { + return attrDef; + } + } + return null; + } + + public Collection getDependencies() { + if (dependencies == null) { + ResourceObjectTypeDefinitionType resourceAccountTypeDefinitionType = getResourceObjectTypeDefinitionType(); + if (resourceAccountTypeDefinitionType == null) { + // No dependencies. But we cannot set null as that means "unknown". So let's set empty collection instead. + dependencies = new ArrayList<>(); + } else { + dependencies = resourceAccountTypeDefinitionType.getDependency(); + } + } + return dependencies; + } + + public SecurityPolicyType getProjectionSecurityPolicy() { + return projectionSecurityPolicy; + } + + public void setProjectionSecurityPolicy(SecurityPolicyType projectionSecurityPolicy) { + this.projectionSecurityPolicy = projectionSecurityPolicy; + } + + public void setCanProject(boolean canProject) { + this.canProject = canProject; + } + + public boolean isCanProject() { + return canProject; + } + + public AssignmentPolicyEnforcementType getAssignmentPolicyEnforcementType() throws SchemaException { + // TODO: per-resource assignment enforcement + ResourceType resource = getResource(); + ProjectionPolicyType objectClassProjectionPolicy = determineObjectClassProjectionPolicy(); + + if (objectClassProjectionPolicy != null && objectClassProjectionPolicy.getAssignmentPolicyEnforcement() != null) { + return MiscSchemaUtil.getAssignmentPolicyEnforcementType(objectClassProjectionPolicy); + } + + ProjectionPolicyType globalAccountSynchronizationSettings = null; + if (resource != null){ + globalAccountSynchronizationSettings = resource.getProjection(); + } + + if (globalAccountSynchronizationSettings == null) { + globalAccountSynchronizationSettings = getLensContext().getAccountSynchronizationSettings(); + } + AssignmentPolicyEnforcementType globalAssignmentPolicyEnforcement = MiscSchemaUtil.getAssignmentPolicyEnforcementType(globalAccountSynchronizationSettings); + return globalAssignmentPolicyEnforcement; + } + + public boolean isLegalize() throws SchemaException { + ResourceType resource = getResource(); + + ProjectionPolicyType objectClassProjectionPolicy = determineObjectClassProjectionPolicy(); + if (objectClassProjectionPolicy != null) { + return BooleanUtils.isTrue(objectClassProjectionPolicy.isLegalize()); + } + ProjectionPolicyType globalAccountSynchronizationSettings = null; + if (resource != null){ + globalAccountSynchronizationSettings = resource.getProjection(); + } + + if (globalAccountSynchronizationSettings == null) { + globalAccountSynchronizationSettings = getLensContext().getAccountSynchronizationSettings(); + } + + if (globalAccountSynchronizationSettings == null){ + return false; + } + + return BooleanUtils.isTrue(globalAccountSynchronizationSettings.isLegalize()); + } + + private ProjectionPolicyType determineObjectClassProjectionPolicy() throws SchemaException { + RefinedResourceSchema refinedSchema = getRefinedResourceSchema(); + if (refinedSchema == null) { + return null; + } + + RefinedObjectClassDefinition objectClassDef = refinedSchema.getRefinedDefinition(resourceShadowDiscriminator.getKind(), + resourceShadowDiscriminator.getIntent()); + + if (objectClassDef == null) { + return null; + } + return objectClassDef.getProjection(); + } + + /** + * Recomputes the new state of account (accountNew). It is computed by applying deltas to the old state (accountOld). + * Assuming that oldAccount is already set (or is null if it does not exist) + */ + public void recompute() throws SchemaException { + ObjectDelta accDelta = getDelta(); + + PrismObject base = getObjectCurrent(); + if (base == null) { + base = getObjectOld(); + } + ObjectDelta syncDelta = getSyncDelta(); + if (base == null && syncDelta != null + && ChangeType.ADD.equals(syncDelta.getChangeType())) { + PrismObject objectToAdd = syncDelta.getObjectToAdd(); + if (objectToAdd != null) { + PrismObjectDefinition objectDefinition = objectToAdd.getDefinition(); + // TODO: remove constructor, use some factory method instead + base = getNotNullPrismContext().itemFactory().createObject(objectToAdd.getElementName(), objectDefinition); + base = syncDelta.computeChangedObject(base); + } + } + + if (accDelta == null) { + // No change + setObjectNew(base); + return; + } + + if (base == null && accDelta.isModify()) { + RefinedObjectClassDefinition rOCD = getCompositeObjectClassDefinition(); + if (rOCD != null) { + base = rOCD.createBlankShadow(); + } + } + + setObjectNew(accDelta.computeChangedObject(base)); + } + + public void clearIntermediateResults() { + //constructionDeltaSetTriple = null; + outboundConstruction = null; + squeezedAttributes = null; + } + + /** + * Returns delta suitable for execution. The primary and secondary deltas may not make complete sense all by themselves. + * E.g. they may both be MODIFY deltas even in case that the account should be created. The deltas begin to make sense + * only if combined with sync decision. This method provides the deltas all combined and ready for execution. + */ + @Override + public ObjectDelta getExecutableDelta() throws SchemaException { + SynchronizationPolicyDecision policyDecision = getSynchronizationPolicyDecision(); + ObjectDelta origDelta = getFixedDelta(); + if (policyDecision == SynchronizationPolicyDecision.ADD) { + // let's try to retrieve original (non-fixed) delta. Maybe it's ADD delta so we spare fixing it. + origDelta = getDelta(); + if (origDelta == null || origDelta.isModify()) { + // We need to convert modify delta to ADD + ObjectDelta addDelta = getPrismContext().deltaFactory().object().create(getObjectTypeClass(), + ChangeType.ADD); + RefinedObjectClassDefinition rObjectClassDef = getCompositeObjectClassDefinition(); + + if (rObjectClassDef == null) { + throw new IllegalStateException("Definition for account type " + getResourceShadowDiscriminator() + + " not found in the context, but it should be there"); + } + PrismObject newAccount = rObjectClassDef.createBlankShadow(); + addDelta.setObjectToAdd(newAccount); + + if (origDelta != null) { + addDelta.merge(origDelta); + } + return addDelta; + } + } else if (policyDecision == SynchronizationPolicyDecision.KEEP) { + // Any delta is OK + } else if (policyDecision == SynchronizationPolicyDecision.DELETE) { + ObjectDelta deleteDelta = getPrismContext().deltaFactory().object().create(getObjectTypeClass(), + ChangeType.DELETE); + String oid = getOid(); + if (oid == null) { + throw new IllegalStateException( + "Internal error: account context OID is null during attempt to create delete secondary delta; context=" + +this); + } + deleteDelta.setOid(oid); + return deleteDelta; + } else { + // This is either UNLINK or null, both are in fact the same as KEEP + // Any delta is OK + } + if (origDelta != null && origDelta.isImmutable()) { + // E.g. locked primary delta. + // We need modifiable delta for execution, e.g. to set metadata, oid and so on. + return origDelta.clone(); + } else { + return origDelta; + } + } + + public void checkConsistence() { + checkConsistence(null, true, false); + } + + @Override + public void checkConsistence(String contextDesc) { + super.checkConsistence(contextDesc); + if (secondaryDelta != null) { + boolean requireOid = isRequireSecondaryDeltaOid(); + // Secondary delta may not have OID yet (as it may relate to ADD primary delta that doesn't have OID yet) + checkConsistence(secondaryDelta, requireOid, getElementDesc() + " secondary delta in " + this + (contextDesc == null ? "" : " in " + contextDesc)); + } + } + + public void checkConsistence(String contextDesc, boolean fresh, boolean force) { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.IGNORE) { + // No not check these. they may be quite wild. + return; + } + super.checkConsistence(contextDesc); + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.BROKEN) { + return; + } + if (fresh && !force && resourceShadowDiscriminator != null && !resourceShadowDiscriminator.isTombstone()) { + if (resource == null) { + throw new IllegalStateException("Null resource in "+this + (contextDesc == null ? "" : " in " +contextDesc)); + } + if (resourceShadowDiscriminator == null) { + throw new IllegalStateException("Null resource account type in "+this + (contextDesc == null ? "" : " in " +contextDesc)); + } + } + if (syncDelta != null) { + try { + syncDelta.checkConsistence(true, true, true, ConsistencyCheckScope.THOROUGH); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(e.getMessage()+"; in "+getElementDesc()+" sync delta in "+this + (contextDesc == null ? "" : " in " +contextDesc), e); + } catch (IllegalStateException e) { + throw new IllegalStateException(e.getMessage()+"; in "+getElementDesc()+" sync delta in "+this + (contextDesc == null ? "" : " in " +contextDesc), e); + } + } + } + + @Override + protected void checkConsistence(PrismObject object, String elementDesc, String contextDesc) { + super.checkConsistence(object, elementDesc, contextDesc); + ResourceAttributeContainer attributesContainer = ShadowUtil.getAttributesContainer(object); + if (attributesContainer != null) { + ResourceType resource = getResource(); + if (resource != null) { + String resourceNamespace = ResourceTypeUtil.getResourceNamespace(resource); + for(ResourceAttribute attribute: attributesContainer.getAttributes()) { + QName attrName = attribute.getElementName(); + if (SchemaConstants.NS_ICF_SCHEMA.equals(attrName.getNamespaceURI())) { + continue; + } + if (resourceNamespace.equals(attrName.getNamespaceURI())) { + continue; + } + String desc = elementDesc+" in "+this + (contextDesc == null ? "" : " in " +contextDesc); + throw new IllegalStateException("Invalid namespace for attribute "+attrName+" in "+desc); + } + } + } + } + + protected boolean isRequireSecondaryDeltaOid() { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.ADD || + synchronizationPolicyDecision == SynchronizationPolicyDecision.BROKEN || + synchronizationPolicyDecision == SynchronizationPolicyDecision.IGNORE) { + return false; + } + if (getResourceShadowDiscriminator() != null && getResourceShadowDiscriminator().getOrder() > 0) { + // These may not have the OID yet + return false; + } + return super.isRequireSecondaryDeltaOid(); + } + + @Override + public void cleanup() { + secondaryDelta = null; + resetSynchronizationPolicyDecision(); +// isLegal = null; +// isLegalOld = null; + isAssigned = false; + isAssignedOld = false; // ??? [med] + isActive = false; + } + + @Override + public void normalize() { + super.normalize(); + if (secondaryDelta != null) { + secondaryDelta.normalize(); + } + if (syncDelta != null) { + syncDelta.normalize(); + } + } + +// @Override +// public void reset() { +// super.reset(); +// wave = -1; +// fullShadow = false; +// isAssigned = false; +// isAssignedOld = false; +// isActive = false; +// resetSynchronizationPolicyDecision(); +// constructionDeltaSetTriple = null; +// outboundConstruction = null; +// dependencies = null; +// squeezedAttributes = null; +// accountPasswordPolicy = null; +// } + + protected void resetSynchronizationPolicyDecision() { + if (synchronizationPolicyDecision == SynchronizationPolicyDecision.DELETE || synchronizationPolicyDecision == SynchronizationPolicyDecision.UNLINK) { + toBeArchived = true; + } else if (synchronizationPolicyDecision != null) { + toBeArchived = false; + } + synchronizationPolicyDecision = null; + } + + @Override + public void adopt(PrismContext prismContext) throws SchemaException { + super.adopt(prismContext); + if (syncDelta != null) { + prismContext.adopt(syncDelta); + } + if (secondaryDelta != null) { + prismContext.adopt(secondaryDelta); + } + } + + @Override + public LensProjectionContext clone(LensContext lensContext) { + LensProjectionContext clone = new LensProjectionContext(lensContext, resourceShadowDiscriminator); + copyValues(clone, lensContext); + return clone; + } + + protected void copyValues(LensProjectionContext clone, LensContext lensContext) { + super.copyValues(clone, lensContext); + // do NOT clone transient values such as accountConstructionDeltaSetTriple + // these are not meant to be cloned and they are also not directly cloneable + clone.dependencies = this.dependencies; + clone.doReconciliation = this.doReconciliation; + clone.fullShadow = this.fullShadow; + clone.isAssigned = this.isAssigned; + clone.isAssignedOld = this.isAssignedOld; + clone.outboundConstruction = this.outboundConstruction; + clone.synchronizationPolicyDecision = this.synchronizationPolicyDecision; + clone.resource = this.resource; + clone.resourceShadowDiscriminator = this.resourceShadowDiscriminator; + clone.squeezedAttributes = cloneSqueezedAttributes(); + if (this.syncDelta != null) { + clone.syncDelta = this.syncDelta.clone(); + } + clone.secondaryDelta = cloneDelta(this.secondaryDelta); + clone.wave = this.wave; + clone.synchronizationSource = this.synchronizationSource; + } + + private Map,PrismPropertyDefinition>>> cloneSqueezedAttributes() { + if (squeezedAttributes == null) { + return null; + } + Map,PrismPropertyDefinition>>> clonedMap = new HashMap<>(); + for (Entry,PrismPropertyDefinition>>> entry: squeezedAttributes.entrySet()) { + clonedMap.put(entry.getKey(), entry.getValue().clone(ItemValueWithOrigin::clone)); + } + return clonedMap; + } + + /** + * Returns true if the projection has any value for specified attribute. + */ + public boolean hasValueForAttribute(QName attributeName) { + ItemPath attrPath = ItemPath.create(ShadowType.F_ATTRIBUTES, attributeName); + if (getObjectNew() != null) { + PrismProperty attrNew = getObjectNew().findProperty(attrPath); + if (attrNew != null && !attrNew.isEmpty()) { + return true; + } + } + return false; + } + + private boolean hasValueForAttribute(QName attributeName, Collection> acPpvSet) { + if (acPpvSet == null) { + return false; + } + for (PrismPropertyValue acPpv: acPpvSet) { + Construction ac = acPpv.getValue(); + if (ac.hasValueForAttribute(attributeName)) { + return true; + } + } + return false; + } + + @Override + public void checkEncrypted() { + super.checkEncrypted(); + if (syncDelta != null) { + CryptoUtil.checkEncrypted(syncDelta); + } + if (secondaryDelta != null) { + CryptoUtil.checkEncrypted(secondaryDelta); + } + } + + @Override + public String getHumanReadableName() { + if (humanReadableName == null) { + StringBuilder sb = new StringBuilder(); + sb.append("account("); + String humanReadableAccountIdentifier = getHumanReadableIdentifier(); + if (StringUtils.isEmpty(humanReadableAccountIdentifier)) { + sb.append("no ID"); + } else { + sb.append("ID "); + sb.append(humanReadableAccountIdentifier); + } + ResourceShadowDiscriminator discr = getResourceShadowDiscriminator(); + if (discr != null) { + sb.append(", type '"); + sb.append(discr.getIntent()); + sb.append("', "); + if (discr.getOrder() != 0) { + sb.append("order ").append(discr.getOrder()).append(", "); + } + } else { + sb.append(" (no discriminator) "); + } + sb.append(getResource()); + sb.append(")"); + humanReadableName = sb.toString(); + } + return humanReadableName; + } + + private String getHumanReadableIdentifier() { + PrismObject object = getObjectNew(); + if (object == null) { + object = getObjectOld(); + } + if (object == null) { + object = getObjectCurrent(); + } + if (object == null) { + return null; + } + if (object.canRepresent(ShadowType.class)) { + PrismObject shadow = (PrismObject)object; + Collection> identifiers = ShadowUtil.getPrimaryIdentifiers(shadow); + if (identifiers == null) { + return null; + } + StringBuilder sb = new StringBuilder(); + Iterator> iterator = identifiers.iterator(); + while (iterator.hasNext()) { + ResourceAttribute id = iterator.next(); + sb.append(id.toHumanReadableString()); + if (iterator.hasNext()) { + sb.append(","); + } + } + return sb.toString(); + } else { + return object.toString(); + } + } + + @Override + public String debugDump(int indent) { + return debugDump(indent, true); + } + + public String debugDump(int indent, boolean showTriples) { + StringBuilder sb = new StringBuilder(); + SchemaDebugUtil.indentDebugDump(sb, indent); + sb.append("PROJECTION "); + sb.append(getObjectTypeClass() == null ? "null" : getObjectTypeClass().getSimpleName()); + sb.append(" "); + sb.append(getResourceShadowDiscriminator()); + if (resource != null) { + sb.append(" : "); + sb.append(resource.getName().getOrig()); + } + sb.append("\n"); + SchemaDebugUtil.indentDebugDump(sb, indent + 1); + sb.append("OID: ").append(getOid()); + sb.append(", wave ").append(wave); + if (fullShadow) { + sb.append(", full"); + } else { + sb.append(", shadow"); + } + sb.append(", exists=").append(isExists); + if (!shadowExistsInRepo) { + sb.append(" (shadow not in repo)"); + } + sb.append(", assigned=").append(isAssignedOld).append("->").append(isAssigned); + sb.append(", active=").append(isActive); + sb.append(", legal=").append(isLegalOld).append("->").append(isLegal); + sb.append(", recon=").append(doReconciliation); + sb.append(", canProject=").append(canProject); + sb.append(", syncIntent=").append(getSynchronizationIntent()); + sb.append(", decision=").append(synchronizationPolicyDecision); + if (!isFresh()) { + sb.append(", NOT FRESH"); + } + if (resourceShadowDiscriminator != null && resourceShadowDiscriminator.isTombstone()) { + sb.append(", TOMBSTONE"); + } + if (syncAbsoluteTrigger) { + sb.append(", SYNC TRIGGER"); + } + if (getIteration() != 0) { + sb.append(", iteration=").append(getIteration()).append(" (").append(getIterationToken()).append(")"); + } + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("old"), getObjectOld(), indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("current"), getObjectCurrent(), indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("new"), getObjectNew(), indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("primary delta"), getPrimaryDelta(), indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("secondary delta"), getSecondaryDelta(), indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("sync delta"), getSyncDelta(), indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("executed deltas"), getExecutedDeltas(), indent+1); + + if (showTriples) { + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("constructionDeltaSetTriple"), constructionDeltaSetTriple, indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("outbound account construction"), outboundConstruction, indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("squeezed attributes"), squeezedAttributes, indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("squeezed associations"), squeezedAssociations, indent + 1); + + sb.append("\n"); + DebugUtil.debugDumpWithLabel(sb, getDebugDumpTitle("squeezed auxiliary object classes"), squeezedAuxiliaryObjectClasses, indent + 1); + + // This is just a debug thing +// sb.append("\n"); +// DebugUtil.indentDebugDump(sb, indent); +// sb.append("ACCOUNT dependencies\n"); +// sb.append(DebugUtil.debugDump(dependencies, indent + 1)); + } + + return sb.toString(); + } + + @Override + protected String getElementDefaultDesc() { + return "projection"; + } + + @Override + public String toString() { + return "LensProjectionContext(" + (getObjectTypeClass() == null ? "null" : getObjectTypeClass().getSimpleName()) + ":" + getOid() + + ( resource == null ? "" : " on " + resource ) + ")"; + } + + /** + * Return a human readable name of the projection object suitable for logs. + */ + public String toHumanReadableString() { + if (humanReadableString == null) { + if (resourceShadowDiscriminator == null) { + humanReadableString = "(null" + resource + ")"; + } else if (resource != null) { + humanReadableString = "("+getKindValue(resourceShadowDiscriminator.getKind()) + " ("+resourceShadowDiscriminator.getIntent()+") on " + resource + ")"; + } else { + humanReadableString = "("+getKindValue(resourceShadowDiscriminator.getKind()) + " ("+resourceShadowDiscriminator.getIntent()+") on " + resourceShadowDiscriminator.getResourceOid() + ")"; + } + } + return humanReadableString; + } + + public String getHumanReadableKind() { + if (resourceShadowDiscriminator == null) { + return "resource object"; + } + return getKindValue(resourceShadowDiscriminator.getKind()); + } + + private String getKindValue(ShadowKindType kind) { + if (kind == null) { + return "null"; + } + return kind.value(); + } + + @Override + protected String getElementDesc() { + if (resourceShadowDiscriminator == null) { + return "shadow"; + } + return getKindValue(resourceShadowDiscriminator.getKind()); + } + + void addToPrismContainer(PrismContainer lensProjectionContextTypeContainer, LensContext.ExportType exportType) throws SchemaException { + LensProjectionContextType lensProjectionContextType = lensProjectionContextTypeContainer.createNewValue().asContainerable(); + super.storeIntoLensElementContextType(lensProjectionContextType, exportType); + lensProjectionContextType.setWave(wave); + lensProjectionContextType.setResourceShadowDiscriminator(resourceShadowDiscriminator != null ? + resourceShadowDiscriminator.toResourceShadowDiscriminatorType() : null); + lensProjectionContextType.setFullShadow(fullShadow); + lensProjectionContextType.setIsExists(isExists); + lensProjectionContextType.setSynchronizationPolicyDecision(synchronizationPolicyDecision != null ? synchronizationPolicyDecision.toSynchronizationPolicyDecisionType() : null); + lensProjectionContextType.setDoReconciliation(doReconciliation); + lensProjectionContextType.setSynchronizationSituationDetected(synchronizationSituationDetected); + lensProjectionContextType.setSynchronizationSituationResolved(synchronizationSituationResolved); + if (exportType != LensContext.ExportType.MINIMAL) { + lensProjectionContextType.setSyncDelta(syncDelta != null ? DeltaConvertor.toObjectDeltaType(syncDelta) : null); + lensProjectionContextType + .setSecondaryDelta(secondaryDelta != null ? DeltaConvertor.toObjectDeltaType(secondaryDelta) : null); + lensProjectionContextType.setIsAssigned(isAssigned); + lensProjectionContextType.setIsAssignedOld(isAssignedOld); + lensProjectionContextType.setIsActive(isActive); + lensProjectionContextType.setIsLegal(isLegal); + lensProjectionContextType.setIsLegalOld(isLegalOld); + if (exportType != LensContext.ExportType.REDUCED && projectionSecurityPolicy != null) { + ObjectReferenceType secRef = new ObjectReferenceType(); + secRef.asReferenceValue().setObject(projectionSecurityPolicy.asPrismObject()); + lensProjectionContextType.setProjectionSecurityPolicyRef(secRef); + } + lensProjectionContextType.setSyncAbsoluteTrigger(syncAbsoluteTrigger); + } + } + + public static LensProjectionContext fromLensProjectionContextType(LensProjectionContextType projectionContextType, LensContext lensContext, Task task, OperationResult result) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, ExpressionEvaluationException { + + String objectTypeClassString = projectionContextType.getObjectTypeClass(); + if (StringUtils.isEmpty(objectTypeClassString)) { + throw new SystemException("Object type class is undefined in LensProjectionContextType"); + } + ResourceShadowDiscriminator resourceShadowDiscriminator = ResourceShadowDiscriminator.fromResourceShadowDiscriminatorType( + projectionContextType.getResourceShadowDiscriminator(), false); + + LensProjectionContext projectionContext = new LensProjectionContext(lensContext, resourceShadowDiscriminator); + + projectionContext.retrieveFromLensElementContextType(projectionContextType, task, result); + if (projectionContextType.getSyncDelta() != null) { + projectionContext.syncDelta = DeltaConvertor.createObjectDelta(projectionContextType.getSyncDelta(), lensContext.getPrismContext()); + } else { + projectionContext.syncDelta = null; + } + ObjectDeltaType secondaryDeltaType = projectionContextType.getSecondaryDelta(); + projectionContext.secondaryDelta = secondaryDeltaType != null ? + DeltaConvertor.createObjectDelta(secondaryDeltaType, lensContext.getPrismContext()) : null; + ObjectType object = projectionContextType.getObjectNew() != null ? projectionContextType.getObjectNew() : projectionContextType.getObjectOld(); + projectionContext.fixProvisioningTypeInDelta(projectionContext.secondaryDelta, object, task, result); + + projectionContext.wave = projectionContextType.getWave() != null ? projectionContextType.getWave() : 0; + projectionContext.fullShadow = projectionContextType.isFullShadow() != null ? projectionContextType.isFullShadow() : false; + projectionContext.isAssigned = projectionContextType.isIsAssigned() != null ? projectionContextType.isIsAssigned() : false; + projectionContext.isAssignedOld = projectionContextType.isIsAssignedOld() != null ? projectionContextType.isIsAssignedOld() : false; + projectionContext.isActive = projectionContextType.isIsActive() != null ? projectionContextType.isIsActive() : false; + projectionContext.isLegal = projectionContextType.isIsLegal(); + projectionContext.isLegalOld = projectionContextType.isIsLegalOld(); + projectionContext.isExists = projectionContextType.isIsExists() != null ? projectionContextType.isIsExists() : false; + projectionContext.synchronizationPolicyDecision = SynchronizationPolicyDecision.fromSynchronizationPolicyDecisionType(projectionContextType.getSynchronizationPolicyDecision()); + projectionContext.doReconciliation = projectionContextType.isDoReconciliation() != null ? projectionContextType.isDoReconciliation() : false; + projectionContext.synchronizationSituationDetected = projectionContextType.getSynchronizationSituationDetected(); + projectionContext.synchronizationSituationResolved = projectionContextType.getSynchronizationSituationResolved(); + ObjectReferenceType projectionSecurityPolicyRef = projectionContextType.getProjectionSecurityPolicyRef(); + if (projectionSecurityPolicyRef != null) { + projectionContext.projectionSecurityPolicy = (SecurityPolicyType) projectionSecurityPolicyRef.getObjectable(); + } + projectionContext.syncAbsoluteTrigger = projectionContextType.isSyncAbsoluteTrigger(); + + return projectionContext; + } + + // determines whether full shadow is present, based on operation result got from provisioning + public void determineFullShadowFlag(PrismObject loadedShadow) { + ShadowType shadowType = loadedShadow.asObjectable(); + if (ShadowUtil.isDead(shadowType) || !ShadowUtil.isExists(shadowType)) { + setFullShadow(false); + return; + } + OperationResultType fetchResult = shadowType.getFetchResult(); + if (fetchResult != null + && (fetchResult.getStatus() == OperationResultStatusType.PARTIAL_ERROR + || fetchResult.getStatus() == OperationResultStatusType.FATAL_ERROR)) { // todo what about other kinds of status? [e.g. in-progress] + setFullShadow(false); + } else { + setFullShadow(true); + } + } + + public boolean isToBeArchived() { + return toBeArchived; + } + + public void setToBeArchived(boolean toBeArchived) { + this.toBeArchived = toBeArchived; + } + + public String getResourceOid() { + if (resource != null) { + return resource.getOid(); + } else if (resourceShadowDiscriminator != null) { + return resourceShadowDiscriminator.getResourceOid(); + } else { + return null; + } + } + + public ResourceObjectVolatilityType getVolatility() throws SchemaException { + RefinedObjectClassDefinition structuralObjectClassDefinition = getStructuralObjectClassDefinition(); + if (structuralObjectClassDefinition == null) { + return null; + } + return structuralObjectClassDefinition.getVolatility(); + } + + public boolean hasPendingOperations() { + PrismObject current = getObjectCurrent(); + if (current == null) { + return false; + } + return !current.asObjectable().getPendingOperation().isEmpty(); + } + + @Override + public void forEachDelta(Consumer> consumer) { + super.forEachDelta(consumer); + if (secondaryDelta != null) { + consumer.accept(secondaryDelta); + } + } + + PolyString resolveNameIfKnown(Class objectClass, String oid) { + if (ResourceType.class.equals(objectClass)) { + if (resource != null && oid.equals(resource.getOid())) { + return PolyString.toPolyString(resource.getName()); + } + } else if (ShadowType.class.equals(objectClass)) { + PrismObject object = getObjectAny(); + if (object != null && oid.equals(object.getOid())) { + if (object.getName() != null) { + return object.getName(); + } else { + try { + return ShadowUtil.determineShadowName(object); + } catch (SchemaException e) { + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't determine shadow name for {}", e, object); + return null; + } + } + } + } + return null; + } + + public String getResourceName() { + ResourceType resource = getResource(); + return resource != null ? PolyString.getOrig(resource.getName()) : getResourceOid(); + } + + public boolean isSynchronizationSource() { + return synchronizationSource; + } + + public void setSynchronizationSource(boolean synchronizationSource) { + this.synchronizationSource = synchronizationSource; + } + + public String getDescription() { + if (resource != null) { + return resource + "("+ resourceShadowDiscriminator.getIntent()+")"; + } else { + if (resourceShadowDiscriminator != null) { + return resourceShadowDiscriminator.toString(); + } else { + return "(UNKNOWN)"; + } + } + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java index 02e25894415..d6c01110bba 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensUtil.java @@ -1,1168 +1,1168 @@ -/* - * Copyright (c) 2010-2019 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; - -import java.util.*; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -import javax.xml.datatype.XMLGregorianCalendar; -import javax.xml.namespace.QName; - -import com.evolveum.midpoint.common.refinery.RefinedResourceSchemaImpl; -import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; -import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRuleTrigger; -import com.evolveum.midpoint.model.common.mapping.PrismValueDeltaSetTripleProducer; -import com.evolveum.midpoint.prism.path.ItemName; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.prism.polystring.PolyString; -import com.evolveum.midpoint.prism.util.ItemDeltaItem; -import com.evolveum.midpoint.repo.api.RepositoryService; -import com.evolveum.midpoint.schema.SchemaConstantsGenerated; -import com.evolveum.midpoint.schema.util.*; -import com.evolveum.midpoint.util.DebugUtil; -import com.evolveum.midpoint.util.LocalizableMessage; -import com.evolveum.midpoint.util.QNameUtil; -import com.evolveum.midpoint.util.exception.*; -import com.evolveum.midpoint.util.logging.LoggingUtils; - -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CredentialsCapabilityType; - -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang.BooleanUtils; - -import com.evolveum.midpoint.common.ActivationComputer; -import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; -import com.evolveum.midpoint.common.refinery.RefinedResourceSchema; -import com.evolveum.midpoint.model.common.mapping.MappingImpl; -import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; -import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.model.impl.util.ModelImplUtils; -import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.ItemDelta; -import com.evolveum.midpoint.prism.delta.ObjectDelta; -import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; -import com.evolveum.midpoint.prism.delta.PropertyDelta; -import com.evolveum.midpoint.provisioning.api.ProvisioningService; -import com.evolveum.midpoint.repo.common.ObjectResolver; -import com.evolveum.midpoint.repo.common.expression.Expression; -import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; -import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.repo.common.expression.Source; -import com.evolveum.midpoint.schema.CapabilityUtil; -import com.evolveum.midpoint.schema.GetOperationOptions; -import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; -import com.evolveum.midpoint.schema.ResultHandler; -import com.evolveum.midpoint.schema.SelectorOptions; -import com.evolveum.midpoint.schema.VirtualAssignmenetSpecification; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.task.api.Task; -import com.evolveum.midpoint.util.DOMUtil; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; -import org.jetbrains.annotations.NotNull; - -import static com.evolveum.midpoint.util.MiscUtil.getSingleValue; -import static java.util.Collections.emptySet; - -/** - * @author semancik - * - */ -public class LensUtil { - - private static final Trace LOGGER = TraceManager.getTrace(LensUtil.class); - - public static ResourceType getResourceReadOnly(LensContext context, - String resourceOid, ProvisioningService provisioningService, Task task, OperationResult result) throws ObjectNotFoundException, - CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - ResourceType resourceType = context.getResource(resourceOid); - if (resourceType == null) { - // Fetching from provisioning to take advantage of caching and - // pre-parsed schema - Collection> options = SelectorOptions.createCollection(GetOperationOptions.createReadOnly()); - resourceType = provisioningService.getObject(ResourceType.class, resourceOid, options, task, result) - .asObjectable(); - context.rememberResource(resourceType); - } - return resourceType; - } - - @NotNull - static ResourceType getResourceReadOnly(LensContext context, String resourceOid, ObjectResolver objectResolver, - Task task, OperationResult result) throws ObjectNotFoundException, - CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - ResourceType resourceFromContext = context.getResource(resourceOid); - if (resourceFromContext != null) { - return resourceFromContext; - } else { - Collection> options = SelectorOptions.createCollection(GetOperationOptions.createReadOnly()); - ResourceType resourceFetched = objectResolver.getObject(ResourceType.class, resourceOid, options, task, result); - context.rememberResource(resourceFetched); - return resourceFetched; - } - } - - public static String refineProjectionIntent(ShadowKindType kind, String intent, ResourceType resource, PrismContext prismContext) throws SchemaException { - RefinedResourceSchema refinedSchema = RefinedResourceSchemaImpl.getRefinedSchema(resource, LayerType.MODEL, prismContext); - RefinedObjectClassDefinition rObjClassDef = refinedSchema.getRefinedDefinition(kind, intent); - if (rObjClassDef == null) { - LOGGER.error("No projection definition for kind={}, intent={} in {}", kind, intent, resource); - LOGGER.error("Diagnostic output follows:\n\nResource:\n{}\n\nRefined resource schema:\n{}", - resource.asPrismObject().debugDump(), refinedSchema.debugDump()); - throw new SchemaException("No projection definition for kind="+kind+" intent="+intent+" in "+resource); - } - return rObjClassDef.getIntent(); - } - - public static LensProjectionContext getProjectionContext(LensContext context, PrismObject equivalentAccount, - ProvisioningService provisioningService, PrismContext prismContext, - Task task, OperationResult result) throws ObjectNotFoundException, - CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - ShadowType equivalentAccountType = equivalentAccount.asObjectable(); - ShadowKindType kind = ShadowUtil.getKind(equivalentAccountType); - return getProjectionContext(context, ShadowUtil.getResourceOid(equivalentAccountType), - kind, equivalentAccountType.getIntent(), equivalentAccountType.getTag(), provisioningService, - prismContext, task, result); - } - - private static LensProjectionContext getProjectionContext(LensContext context, String resourceOid, - ShadowKindType kind, String intent, String tag, - ProvisioningService provisioningService, PrismContext prismContext, - Task task, OperationResult result) throws ObjectNotFoundException, - CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - ResourceType resource = getResourceReadOnly(context, resourceOid, provisioningService, task, result); - String refinedIntent = refineProjectionIntent(kind, intent, resource, prismContext); - ResourceShadowDiscriminator rsd = new ResourceShadowDiscriminator(resourceOid, kind, refinedIntent, tag, false); - return context.findProjectionContext(rsd); - } - - public static LensProjectionContext getOrCreateProjectionContext(LensContext context, - ResourceShadowDiscriminator rsd) { - LensProjectionContext accountSyncContext = context.findProjectionContext(rsd); - if (accountSyncContext == null) { - accountSyncContext = context.createProjectionContext(rsd); - ResourceType resource = context.getResource(rsd.getResourceOid()); - accountSyncContext.setResource(resource); - } - accountSyncContext.setDoReconciliation(context.isDoReconciliationForAllProjections()); - return accountSyncContext; - } - - public static LensProjectionContext createAccountContext(LensContext context, ResourceShadowDiscriminator rsd){ - return new LensProjectionContext(context, rsd); - } - - public static V cloneAndApplyMetadata(V value, boolean isAssignment, - Collection> origins) throws SchemaException { - return cloneAndApplyMetadata(value, isAssignment, () -> getAutoCreationIdentifier(origins)); - } - -// public static Collection cloneAndApplyMetadata(Collection values, boolean isAssignment, -// MappingType mapping) throws SchemaException { -// List rv = new ArrayList<>(); -// for (V value : values) { -// rv.add(cloneAndApplyMetadata(value, isAssignment, mapping::getName)); -// } -// return rv; -// } - - public static V cloneAndApplyMetadata(V value, boolean isAssignment, - PrismValueDeltaSetTripleProducer mapping) throws SchemaException { - return cloneAndApplyMetadata(value, isAssignment, mapping::getIdentifier); - } - - public static V cloneAndApplyMetadata(V value, boolean isAssignment, - MappingType mapping) throws SchemaException { - return cloneAndApplyMetadata(value, isAssignment, mapping::getName); - } - - private static V cloneAndApplyMetadata(V value, boolean isAssignment, - Supplier originMappingNameSupplier) throws SchemaException { - //noinspection unchecked - V cloned = (V) value.clone(); - if (isAssignment && cloned instanceof PrismContainerValue) { - ((PrismContainerValue) cloned).setId(null); - String originMappingName = originMappingNameSupplier.get(); - LOGGER.trace("cloneAndApplyMetadata: originMappingName = {}", originMappingName); - if (originMappingName != null) { - //noinspection unchecked - PrismContainer metadataContainer = ((PrismContainerValue) cloned).findOrCreateContainer(AssignmentType.F_METADATA); - metadataContainer.getValue().asContainerable().setOriginMappingName(originMappingName); - } - } - return cloned; - } - - private static String getAutoCreationIdentifier(Collection> origins) { - // let's ignore conflicts (name1 vs name2, named vs unnamed) for now - for (ItemValueWithOrigin origin : origins) { - if (origin.getMapping() != null && origin.getMapping().getIdentifier() != null) { - return origin.getMapping().getIdentifier(); - } - } - return null; - } - - public static PropertyDelta createActivationTimestampDelta(ActivationStatusType status, - XMLGregorianCalendar now, - PrismContainerDefinition activationDefinition, OriginType origin, - PrismContext prismContext) { - ItemName timestampPropertyName; - if (status == null || status == ActivationStatusType.ENABLED) { - timestampPropertyName = ActivationType.F_ENABLE_TIMESTAMP; - } else if (status == ActivationStatusType.DISABLED) { - timestampPropertyName = ActivationType.F_DISABLE_TIMESTAMP; - } else if (status == ActivationStatusType.ARCHIVED) { - timestampPropertyName = ActivationType.F_ARCHIVE_TIMESTAMP; - } else { - throw new IllegalArgumentException("Unknown activation status "+status); - } - - PrismPropertyDefinition timestampDef = activationDefinition.findPropertyDefinition(timestampPropertyName); - PropertyDelta timestampDelta - = timestampDef.createEmptyDelta(FocusType.F_ACTIVATION.append(timestampPropertyName)); - timestampDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(now, origin, null)); - return timestampDelta; - } - - public static void moveTriggers(LensProjectionContext projCtx, LensFocusContext focusCtx) throws SchemaException { - ObjectDelta projSecondaryDelta = projCtx.getSecondaryDelta(); - if (projSecondaryDelta == null) { - return; - } - Collection modifications = projSecondaryDelta.getModifications(); - Iterator iterator = modifications.iterator(); - while (iterator.hasNext()) { - ItemDelta projModification = iterator.next(); - LOGGER.trace("MOD: {}\n{}", projModification.getPath(), projModification.debugDumpLazily()); - if (projModification.getPath().equivalent(SchemaConstants.PATH_TRIGGER)) { - focusCtx.swallowToProjectionWaveSecondaryDelta(projModification); - iterator.remove(); - } - } - } - - public static Object getIterationVariableValue(LensProjectionContext accCtx) { - Integer iterationOld = null; - PrismObject shadowCurrent = accCtx.getObjectCurrent(); - if (shadowCurrent != null) { - iterationOld = shadowCurrent.asObjectable().getIteration(); - } - if (iterationOld == null) { - return accCtx.getIteration(); - } - PrismPropertyDefinition propDef = accCtx.getPrismContext().definitionFactory().createPropertyDefinition(ExpressionConstants.VAR_ITERATION_QNAME, - DOMUtil.XSD_INT); - PrismProperty propOld = propDef.instantiate(); - propOld.setRealValue(iterationOld); - PropertyDelta propDelta = propDef.createEmptyDelta(ExpressionConstants.VAR_ITERATION_QNAME); - propDelta.setRealValuesToReplace(accCtx.getIteration()); - PrismProperty propNew = propDef.instantiate(); - propNew.setRealValue(accCtx.getIteration()); - ItemDeltaItem,PrismPropertyDefinition> idi = new ItemDeltaItem<>(propOld, propDelta, propNew, propDef); - return idi; - } - - public static Object getIterationTokenVariableValue(LensProjectionContext accCtx) { - String iterationTokenOld = null; - PrismObject shadowCurrent = accCtx.getObjectCurrent(); - if (shadowCurrent != null) { - iterationTokenOld = shadowCurrent.asObjectable().getIterationToken(); - } - if (iterationTokenOld == null) { - return accCtx.getIterationToken(); - } - PrismPropertyDefinition propDef = accCtx.getPrismContext().definitionFactory().createPropertyDefinition( - ExpressionConstants.VAR_ITERATION_TOKEN_QNAME, DOMUtil.XSD_STRING); - PrismProperty propOld = propDef.instantiate(); - propOld.setRealValue(iterationTokenOld); - PropertyDelta propDelta = propDef.createEmptyDelta(ExpressionConstants.VAR_ITERATION_TOKEN_QNAME); - propDelta.setRealValuesToReplace(accCtx.getIterationToken()); - PrismProperty propNew = propDef.instantiate(); - propNew.setRealValue(accCtx.getIterationToken()); - ItemDeltaItem,PrismPropertyDefinition> idi = new ItemDeltaItem<>(propOld, propDelta, propNew, propDef); - return idi; - } - - /** - * Extracts the delta from this projection context and also from all other projection contexts that have - * equivalent discriminator. - */ - public static PropertyDelta findAPrioriDelta(LensContext context, - LensProjectionContext projCtx, ItemPath projectionPropertyPath) throws SchemaException { - PropertyDelta aPrioriDelta = null; - for (LensProjectionContext aProjCtx: findRelatedContexts(context, projCtx)) { - ObjectDelta aProjDelta = aProjCtx.getDelta(); - if (aProjDelta != null) { - PropertyDelta aPropProjDelta = aProjDelta.findPropertyDelta(projectionPropertyPath); - if (aPropProjDelta != null) { - if (aPrioriDelta == null) { - aPrioriDelta = aPropProjDelta.clone(); - } else { - aPrioriDelta.merge(aPropProjDelta); - } - } - } - } - return aPrioriDelta; - } - - /** - * Extracts the delta from this projection context and also from all other projection contexts that have - * equivalent discriminator. - */ - public static ObjectDelta findAPrioriDelta(LensContext context, - LensProjectionContext projCtx) throws SchemaException { - ObjectDelta aPrioriDelta = null; - for (LensProjectionContext aProjCtx: findRelatedContexts(context, projCtx)) { - ObjectDelta aProjDelta = aProjCtx.getDelta(); - if (aProjDelta != null) { - if (aPrioriDelta == null) { - aPrioriDelta = aProjDelta.clone(); - } else { - aPrioriDelta.merge(aProjDelta); - } - } - } - return aPrioriDelta; - } - - /** - * Returns a list of context that have equivalent discriminator with the reference context. Ordered by "order" in the - * discriminator. - */ - public static List findRelatedContexts( - LensContext context, LensProjectionContext refProjCtx) { - List projCtxs = new ArrayList<>(); - ResourceShadowDiscriminator refDiscr = refProjCtx.getResourceShadowDiscriminator(); - if (refDiscr == null) { - return projCtxs; - } - for (LensProjectionContext aProjCtx: context.getProjectionContexts()) { - ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator(); - if (refDiscr.equivalent(aDiscr)) { - projCtxs.add(aProjCtx); - } - } - Comparator orderComparator = new Comparator() { - @Override - public int compare(LensProjectionContext ctx1, LensProjectionContext ctx2) { - int order1 = ctx1.getResourceShadowDiscriminator().getOrder(); - int order2 = ctx2.getResourceShadowDiscriminator().getOrder(); - return Integer.compare(order1, order2); - } - }; - Collections.sort(projCtxs, orderComparator); - return projCtxs; - } - - public static boolean hasLowerOrderContext(LensContext context, - LensProjectionContext refProjCtx) { - ResourceShadowDiscriminator refDiscr = refProjCtx.getResourceShadowDiscriminator(); - for (LensProjectionContext aProjCtx: context.getProjectionContexts()) { - ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator(); - if (refDiscr.equivalent(aDiscr) && (refDiscr.getOrder() > aDiscr.getOrder())) { - return true; - } - } - return false; - } - - public static boolean hasDependentContext(LensContext context, - LensProjectionContext targetProjectionContext) { - for (LensProjectionContext projectionContext: context.getProjectionContexts()) { - for (ResourceObjectTypeDependencyType dependency: projectionContext.getDependencies()) { - if (isDependencyTargetContext(projectionContext, targetProjectionContext, dependency)) { - return true; - } - } - } - return false; - } - - public static boolean isDependencyTargetContext(LensProjectionContext sourceProjContext, LensProjectionContext targetProjectionContext, ResourceObjectTypeDependencyType dependency) { - ResourceShadowDiscriminator refDiscr = new ResourceShadowDiscriminator(dependency, - sourceProjContext.getResource().getOid(), sourceProjContext.getKind()); - return targetProjectionContext.compareResourceShadowDiscriminator(refDiscr, false); - } - - public static LensProjectionContext findLowerOrderContext(LensContext context, - LensProjectionContext refProjCtx) { - int minOrder = -1; - LensProjectionContext foundCtx = null; - ResourceShadowDiscriminator refDiscr = refProjCtx.getResourceShadowDiscriminator(); - for (LensProjectionContext aProjCtx: context.getProjectionContexts()) { - ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator(); - if (refDiscr.equivalent(aDiscr) && (refDiscr.getOrder() > aDiscr.getOrder())) { - if (minOrder < 0 || (aDiscr.getOrder() < minOrder)) { - minOrder = aDiscr.getOrder(); - foundCtx = aProjCtx; - } - } - } - return foundCtx; - } - - public static void setContextOid(LensContext context, - LensElementContext objectContext, String oid) { - objectContext.setOid(oid); - // Check if we need to propagate this oid also to higher-order contexts - if (!(objectContext instanceof LensProjectionContext)) { - return; - } - LensProjectionContext refProjCtx = (LensProjectionContext)objectContext; - ResourceShadowDiscriminator refDiscr = refProjCtx.getResourceShadowDiscriminator(); - if (refDiscr == null) { - return; - } - for (LensProjectionContext aProjCtx: context.getProjectionContexts()) { - ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator(); - if (aDiscr != null && refDiscr.equivalent(aDiscr) && (refDiscr.getOrder() < aDiscr.getOrder())) { - aProjCtx.setOid(oid); - } - } - } - - public static PrismObjectDefinition getFocusDefinition(LensContext context) { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - return null; - } - Class typeClass = focusContext.getObjectTypeClass(); - return context.getPrismContext().getSchemaRegistry().findObjectDefinitionByCompileTimeClass(typeClass); - } - - public static IterationSpecificationType getIterationSpecification(ObjectTemplateType objectTemplate) { - return objectTemplate != null ? objectTemplate.getIterationSpecification() : null; - } - - public static int determineMaxIterations(IterationSpecificationType iterationSpecType) { - return iterationSpecType != null ? iterationSpecType.getMaxIterations() : 0; - } - - public static String formatIterationToken(LensContext context, - LensElementContext accountContext, IterationSpecificationType iterationType, - int iteration, ExpressionFactory expressionFactory, ExpressionVariables variables, - Task task, OperationResult result) - throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - if (iterationType == null) { - return formatIterationTokenDefault(iteration); - } - ExpressionType tokenExpressionType = iterationType.getTokenExpression(); - if (tokenExpressionType == null) { - return formatIterationTokenDefault(iteration); - } - PrismContext prismContext = context.getPrismContext(); - PrismPropertyDefinition outputDefinition = prismContext.definitionFactory().createPropertyDefinition(ExpressionConstants.VAR_ITERATION_TOKEN_QNAME, - DOMUtil.XSD_STRING); - Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression(tokenExpressionType, outputDefinition, MiscSchemaUtil.getExpressionProfile(), "iteration token expression in "+accountContext.getHumanReadableName(), task, result); - - Collection> sources = new ArrayList<>(); - MutablePrismPropertyDefinition inputDefinition = prismContext.definitionFactory().createPropertyDefinition(ExpressionConstants.VAR_ITERATION_QNAME, - DOMUtil.XSD_INT); - inputDefinition.setMaxOccurs(1); - PrismProperty input = inputDefinition.instantiate(); - input.addRealValue(iteration); - ItemDeltaItem,PrismPropertyDefinition> idi = new ItemDeltaItem<>(input); - Source,PrismPropertyDefinition> iterationSource = new Source<>(idi, ExpressionConstants.VAR_ITERATION_QNAME); - sources.add(iterationSource); - - ExpressionEvaluationContext expressionContext = new ExpressionEvaluationContext(sources , variables, - "iteration token expression in "+accountContext.getHumanReadableName(), task); - PrismValueDeltaSetTriple> outputTriple = ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, expressionContext, task, result); - Collection> outputValues = outputTriple.getNonNegativeValues(); - if (outputValues.isEmpty()) { - return ""; - } - if (outputValues.size() > 1) { - throw new ExpressionEvaluationException("Iteration token expression in "+accountContext.getHumanReadableName()+" returned more than one value ("+outputValues.size()+" values)"); - } - String realValue = outputValues.iterator().next().getValue(); - if (realValue == null) { - return ""; - } - return realValue; - } - - public static String formatIterationTokenDefault(int iteration) { - if (iteration == 0) { - return ""; - } - return Integer.toString(iteration); - } - - public static boolean evaluateIterationCondition(LensContext context, - LensElementContext accountContext, IterationSpecificationType iterationType, - int iteration, String iterationToken, boolean beforeIteration, - ExpressionFactory expressionFactory, ExpressionVariables variables, Task task, OperationResult result) - throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - if (iterationType == null) { - return true; - } - ExpressionType expressionType; - String desc; - if (beforeIteration) { - expressionType = iterationType.getPreIterationCondition(); - desc = "pre-iteration expression in "+accountContext.getHumanReadableName(); - } else { - expressionType = iterationType.getPostIterationCondition(); - desc = "post-iteration expression in "+accountContext.getHumanReadableName(); - } - if (expressionType == null) { - return true; - } - Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression( - expressionType, ExpressionUtil.createConditionOutputDefinition(context.getPrismContext()), MiscSchemaUtil.getExpressionProfile(), - desc, task, result); - - variables.put(ExpressionConstants.VAR_ITERATION, iteration, Integer.class); - variables.put(ExpressionConstants.VAR_ITERATION_TOKEN, iterationToken, String.class); - - ExpressionEvaluationContext expressionContext = new ExpressionEvaluationContext(null , variables, desc, task); - ExpressionEnvironment env = new ExpressionEnvironment<>(context, null, task, result); - PrismValueDeltaSetTriple> outputTriple = - ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, expressionContext, env, result); - Collection> outputValues = outputTriple.getNonNegativeValues(); - if (outputValues.isEmpty()) { - return false; - } - if (outputValues.size() > 1) { - throw new ExpressionEvaluationException(desc+" returned more than one value ("+outputValues.size()+" values)"); - } - Boolean realValue = outputValues.iterator().next().getValue(); - if (realValue == null) { - return false; - } - return realValue; - - } - - /** - * Used for assignments and similar objects that do not have separate lifecycle. - */ - public static boolean isAssignmentValid(AssignmentHolderType focus, AssignmentType assignment, XMLGregorianCalendar now, - ActivationComputer activationComputer, LifecycleStateModelType focusStateModel) { - ObjectReferenceType targetRef = assignment.getTargetRef(); - if (targetRef != null && QNameUtil.match(ArchetypeType.COMPLEX_TYPE, targetRef.getType())) { - // Archetype assignments are always valid, even in non-valid lifecycle states. - // The object cannot lose its (arche)type. - return true; - } - String focusLifecycleState = focus.getLifecycleState(); - - if (!activationComputer.lifecycleHasActiveAssignments(focusLifecycleState, focusStateModel)) { - return false; - } - return isValid(assignment.getLifecycleState(), assignment.getActivation(), now, activationComputer, focusStateModel); - } - - public static Collection getForcedAssignments(LifecycleStateModelType lifecycleModel, String targetLifecycle, - ObjectResolver objectResolver, PrismContext prismContext, Task task, OperationResult result) throws SchemaException, - ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - VirtualAssignmenetSpecification virtualAssignmenetSpecification = LifecycleUtil.getForcedAssignmentSpecification(lifecycleModel, targetLifecycle, prismContext); - - Collection forcedAssignments = new HashSet<>(); - if (virtualAssignmenetSpecification != null) { - - ResultHandler handler = (object, parentResult) -> { - AssignmentType assignment = ObjectTypeUtil.createAssignmentTo(object, prismContext); - return forcedAssignments.add(assignment); - }; - - objectResolver.searchIterative(virtualAssignmenetSpecification.getType(), - prismContext.queryFactory().createQuery(virtualAssignmenetSpecification.getFilter()), null, handler, task, result); - - } - - return forcedAssignments; - } - - public static boolean isFocusValid(AssignmentHolderType focus, XMLGregorianCalendar now, ActivationComputer activationComputer, LifecycleStateModelType focusStateModel) { - if (FocusType.class.isAssignableFrom(focus.getClass())) { - return isValid(focus.getLifecycleState(), ((FocusType) focus).getActivation(), now, activationComputer, focusStateModel); - } - return isValid(focus.getLifecycleState(), null, now, activationComputer, focusStateModel); - } - - private static boolean isValid(String lifecycleState, ActivationType activationType, XMLGregorianCalendar now, ActivationComputer activationComputer, LifecycleStateModelType focusStateModel) { - TimeIntervalStatusType validityStatus = activationComputer.getValidityStatus(activationType, now); - ActivationStatusType effectiveStatus = activationComputer.getEffectiveStatus(lifecycleState, activationType, validityStatus, focusStateModel); - return effectiveStatus == ActivationStatusType.ENABLED; - } - - public static AssignmentPathVariables computeAssignmentPathVariables(AssignmentPathImpl assignmentPath) throws SchemaException { - if (assignmentPath == null || assignmentPath.isEmpty()) { - return null; - } - AssignmentPathVariables vars = new AssignmentPathVariables(); - vars.setAssignmentPath(assignmentPath.clone()); - - Iterator iterator = assignmentPath.getSegments().iterator(); - while (iterator.hasNext()) { - AssignmentPathSegmentImpl segment = iterator.next(); - ItemDeltaItem,PrismContainerDefinition> segmentAssignmentIdi = segment.getAssignmentIdi(); - - ItemDeltaItem,PrismContainerDefinition> magicAssignmentIdi; - // Magic assignment - if (vars.getMagicAssignment() == null) { - magicAssignmentIdi = segmentAssignmentIdi.clone(); - vars.setMagicAssignment(magicAssignmentIdi); - } else { - // Collect extension values from the assignment extension - magicAssignmentIdi = vars.getMagicAssignment(); - mergeExtension(magicAssignmentIdi, segmentAssignmentIdi); - } - - // Collect extension values from the source object extension - ObjectType segmentSource = segment.getSource(); - if (segmentSource != null) { - mergeExtension(magicAssignmentIdi, segmentSource.asPrismObject()); - } - - // immediate assignment (use assignment from previous iteration) - vars.setImmediateAssignment(vars.getThisAssignment()); - - // this assignment - ItemDeltaItem,PrismContainerDefinition> thisAssignment = segmentAssignmentIdi.clone(); - vars.setThisAssignment(thisAssignment); - - if (iterator.hasNext() && segmentSource instanceof AbstractRoleType) { - vars.setImmediateRole((PrismObject) segmentSource.asPrismObject()); - } - } - - AssignmentPathSegmentImpl focusAssignmentSegment = assignmentPath.first(); - vars.setFocusAssignment(focusAssignmentSegment.getAssignmentIdi().clone()); - - // a bit of hack -- TODO reconsider in 3.7 - // objects are already cloned - convertToLegacy(vars.getMagicAssignment()); - convertToLegacy(vars.getThisAssignment()); - convertToLegacy(vars.getFocusAssignment()); - convertToLegacy(vars.getImmediateAssignment()); - - return vars; - } - - private static void convertToLegacy( - ItemDeltaItem, PrismContainerDefinition> idi) { - if (idi == null || idi.getDelta() == null || idi.getSubItemDeltas() != null) { - return; - } - // Legacy approach (when adding/removing assignments) was: itemOld+itemNew = value, delta = null - // This was recently changed, to provide precise information (add = null->itemNew, delete = itemOld->null). - // However, to not break scripts before 3.6 release we provide imitation of old behavior here. - // (Moreover, for magic assignment the delta is not correct anyway.) - if (idi.getDelta().isAdd() || idi.getDelta().isReplace()) { - idi.setItemOld(idi.getItemNew().clone()); - } else { - idi.setItemNew(idi.getItemOld().clone()); - } - idi.setDelta(null); - } - - private static void mergeExtension(ItemDeltaItem,PrismContainerDefinition> destIdi, ItemDeltaItem,PrismContainerDefinition> srcIdi) throws SchemaException { - mergeExtension(destIdi.getItemOld(), srcIdi.getItemOld()); - mergeExtension(destIdi.getItemNew(), srcIdi.getItemNew()); - if (srcIdi.getDelta() != null || srcIdi.getSubItemDeltas() != null) { - throw new UnsupportedOperationException("Merge of IDI with deltas not supported"); - } - } - - private static void mergeExtension(Item,PrismContainerDefinition> dstItem, - Item,PrismContainerDefinition> srcItem) throws SchemaException { - if (srcItem == null || dstItem == null) { - return; - } - PrismContainer srcExtension = ((PrismContainer)srcItem).findContainer(AssignmentType.F_EXTENSION); - mergeExtensionContainers(dstItem, srcExtension); - } - - private static void mergeExtension(ItemDeltaItem,PrismContainerDefinition> destIdi, - PrismObject srcObject) throws SchemaException { - if (srcObject == null) { - return; - } - - PrismContainer srcExtension = srcObject.findContainer(ObjectType.F_EXTENSION); - - mergeExtensionContainers(destIdi.getItemNew(), srcExtension); - mergeExtensionContainers(destIdi.getItemOld(), srcExtension); - } - - private static void mergeExtensionContainers(Item,PrismContainerDefinition> dstItem, PrismContainer srcExtension) throws SchemaException { - if (dstItem == null) { - return; - } - PrismContainer dstContainer = (PrismContainer) dstItem; - if (srcExtension != null && !srcExtension.isEmpty()) { - PrismContainer dstExtensionContainer = dstContainer.findOrCreateContainer(AssignmentType.F_EXTENSION); - PrismContainerValue dstExtensionContainerValue = dstExtensionContainer.getValues().isEmpty() - ? dstExtensionContainer.createNewValue() : dstExtensionContainer.getValue(); - ObjectTypeUtil.mergeExtension(dstExtensionContainerValue, srcExtension.getValue()); - } - } - - public static MappingImpl.Builder addAssignmentPathVariables(MappingImpl.Builder builder, AssignmentPathVariables assignmentPathVariables, PrismContext prismContext) { - ExpressionVariables expressionVariables = new ExpressionVariables(); - ModelImplUtils.addAssignmentPathVariables(assignmentPathVariables, expressionVariables, prismContext); - return builder.addVariableDefinitions(expressionVariables); - } - - public static void checkContextSanity(LensContext context, String activityDescription, - OperationResult result) throws SchemaException, PolicyViolationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext != null) { - PrismObject focusObjectNew = focusContext.getObjectNew(); - if (focusObjectNew != null) { - PolyStringType namePolyType = focusObjectNew.asObjectable().getName(); - if (namePolyType == null) { - throw new SchemaException("Focus "+focusObjectNew+" does not have a name after "+activityDescription); - } - ArchetypePolicyType archetypePolicy = focusContext.getArchetypePolicyType(); - checkArchetypePolicy(focusContext, archetypePolicy); - } - } - } - - private static void checkArchetypePolicy(LensFocusContext focusContext, ArchetypePolicyType archetypePolicy) throws SchemaException, PolicyViolationException { - if (archetypePolicy == null) { - return; - } - PrismObject focusObjectNew = focusContext.getObjectNew(); - ObjectDelta focusDelta = focusContext.getDelta(); - - for (ItemConstraintType itemConstraintType : archetypePolicy.getItemConstraint()) { - processItemConstraint(focusContext, focusDelta, focusObjectNew, itemConstraintType); - } - // Deprecated - for (ItemConstraintType itemConstraintType : archetypePolicy.getPropertyConstraint()) { - processItemConstraint(focusContext, focusDelta, focusObjectNew, itemConstraintType); - } - - } - - private static void processItemConstraint(LensFocusContext focusContext, ObjectDelta focusDelta, PrismObject focusObjectNew, ItemConstraintType itemConstraintType) throws PolicyViolationException { - ItemPath itemPath = itemConstraintType.getPath().getItemPath(); - if (BooleanUtils.isTrue(itemConstraintType.isOidBound())) { - if (focusDelta != null) { - if (focusDelta.isAdd()) { - PrismProperty propNew = focusObjectNew.findProperty(itemPath); - if (propNew != null) { - // prop delta is OK, but it has to match - if (focusObjectNew.getOid() != null) { - if (!focusObjectNew.getOid().equals(propNew.getRealValue().toString())) { - throw new PolicyViolationException("Cannot set "+itemPath+" to a value different than OID in oid bound mode"); - } - } - } - } else { - PropertyDelta nameDelta = focusDelta.findPropertyDelta(itemPath); - if (nameDelta != null) { - if (nameDelta.isReplace()) { - Collection> valuesToReplace = nameDelta.getValuesToReplace(); - if (valuesToReplace.size() == 1) { - String stringValue = valuesToReplace.iterator().next().getValue().toString(); - if (focusContext.getOid().equals(stringValue)) { - // This is OK. It is most likely a correction made by a recompute. - return; - } - } - } - throw new PolicyViolationException("Cannot change "+itemPath+" in oid bound mode"); - } - } - } - } - - } - - public static PrismContainer createAssignmentSingleValueContainer(@NotNull AssignmentType assignmentType) throws SchemaException { - // Make it appear to be single-value. Therefore paths without segment IDs will work. - return assignmentType.asPrismContainerValue().asSingleValuedContainer(SchemaConstantsGenerated.C_ASSIGNMENT); - } - - public static AssignmentType getAssignmentType(ItemDeltaItem,PrismContainerDefinition> assignmentIdi, boolean old) { - return PrismContainerValue.asContainerable(assignmentIdi.getSingleValue(old)); - } - - - public static String getChannel(LensContext context, Task task) { - if (context != null && context.getChannel() != null){ - return context.getChannel(); - } else if (task.getChannel() != null){ - return task.getChannel(); - } - return null; - } - - public static void setDeltaOldValue(LensElementContext ctx, ItemDelta itemDelta) { - if (itemDelta.getEstimatedOldValues() != null) { - return; - } - if (ctx.getObjectOld() == null) { - return; - } - Item itemOld = ctx.getObjectOld().findItem(itemDelta.getPath()); - if (itemOld != null) { - //noinspection unchecked - itemDelta.setEstimatedOldValues((Collection) PrismValueCollectionsUtil.cloneCollection(itemOld.getValues())); - return; - } - // Here we need to distinguish whether the item is missing because it is not filled in (e.g. familyName in MID-4237) - // or because it was not loaded (as for attributes or associations). - if (!isItemLoadable(ctx.getObjectOld(), itemDelta.getPath())) { - itemDelta.setEstimatedOldValues(emptySet()); - return; - } - // get the old data from current object. Still better estimate than nothing - if (ctx.getObjectCurrent() != null) { - itemOld = ctx.getObjectCurrent().findItem(itemDelta.getPath()); - if (itemOld != null) { - //noinspection unchecked - itemDelta.setEstimatedOldValues((Collection) PrismValueCollectionsUtil.cloneCollection(itemOld.getValues())); - } - } - } - - // a heuristic by now - private static boolean isItemLoadable(PrismObject object, ItemPath path) { - if (!(object.asObjectable() instanceof ShadowType)) { - return false; - } - return path.startsWithName(ShadowType.F_ATTRIBUTES) || path.startsWithName(ShadowType.F_ASSOCIATION); - } - - public static void setDeltaOldValue(LensElementContext ctx, ObjectDelta objectDelta) { - if (objectDelta == null) { - return; - } - if (!objectDelta.isModify()) { - return; - } - for (ItemDelta modification: objectDelta.getModifications()) { - setDeltaOldValue(ctx, modification); - } - } - - public static LensObjectDeltaOperation createObjectDeltaOperation(ObjectDelta focusDelta, OperationResult result, - LensElementContext focusContext, LensProjectionContext projCtx) { - return createObjectDeltaOperation(focusDelta, result, focusContext, projCtx, null); - } - - // projCtx may or may not be present (object itself can be focus or projection) - public static LensObjectDeltaOperation createObjectDeltaOperation(ObjectDelta objectDelta, OperationResult result, - LensElementContext objectContext, - LensProjectionContext projCtx, - ResourceType resource) { - LensObjectDeltaOperation objectDeltaOp = new LensObjectDeltaOperation<>(objectDelta.clone()); - objectDeltaOp.setExecutionResult(result); - PrismObject object = objectContext.getObjectAny(); - if (object != null) { - PolyString name = object.getName(); - if (name == null && object.asObjectable() instanceof ShadowType) { - try { - name = ShadowUtil.determineShadowName((PrismObject) object); - if (name == null) { - LOGGER.debug("No name for shadow:\n{}", object.debugDump()); - } else if (name.getNorm() == null) { - name.recompute(objectContext.getPrismContext().getDefaultPolyStringNormalizer()); - } - } catch (SchemaException e) { - LoggingUtils.logUnexpectedException(LOGGER, "Couldn't determine name for shadow -- continuing with no name; shadow:\n{}", e, object.debugDump()); - } - } - objectDeltaOp.setObjectName(name); - } - if (resource == null && projCtx != null) { - resource = projCtx.getResource(); - } - if (resource != null) { - objectDeltaOp.setResourceOid(resource.getOid()); - objectDeltaOp.setResourceName(PolyString.toPolyString(resource.getName())); - } else if (objectContext instanceof LensProjectionContext) { - objectDeltaOp.setResourceOid(((LensProjectionContext) objectContext).getResourceOid()); - } - return objectDeltaOp; - } - - public static void triggerRule(@NotNull EvaluatedPolicyRule rule, Collection> triggers, - Collection policySituations) { - - LOGGER.debug("Policy rule {} triggered: {}", rule.getName(), triggers); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Policy rule {} triggered:\n{}", rule.getName(), DebugUtil.debugDump(triggers, 1)); - } - - ((EvaluatedPolicyRuleImpl) rule).addTriggers(triggers); - CollectionUtils.addIgnoreNull(policySituations, rule.getPolicySituation()); - } - - public static void processRuleWithException(@NotNull EvaluatedPolicyRule rule, Collection> triggers, - PolicyExceptionType policyException) { - - LOGGER.debug("Policy rule {} would be triggered, but there is an exception for it. Not triggering", rule.getName()); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Policy rule {} would be triggered, but there is an exception for it:\nTriggers:\n{}\nException:\n{}", - rule.getName(), DebugUtil.debugDump(triggers, 1), policyException); - } - ((EvaluatedPolicyRuleImpl)rule).addPolicyException(policyException); - } - - - public static void checkMaxIterations(int iteration, int maxIterations, String conflictMessage, String humanReadableName) - throws ObjectAlreadyExistsException { - if (iteration > maxIterations) { - StringBuilder sb = new StringBuilder(); - if (iteration == 1) { - sb.append("Error processing "); - } else { - sb.append("Too many iterations (").append(iteration).append(") for "); - } - sb.append(humanReadableName); - if (iteration == 1) { - sb.append(": constraint violation: "); - } else { - sb.append(": cannot determine values that satisfy constraints: "); - } - if (conflictMessage != null) { - sb.append(conflictMessage); - } - throw new ObjectAlreadyExistsException(sb.toString()); - } - } - - public static boolean needsFullShadowForCredentialProcessing(LensProjectionContext projCtx) throws SchemaException { - RefinedObjectClassDefinition refinedProjDef = projCtx.getStructuralObjectClassDefinition(); - if (refinedProjDef == null) { - return false; - } - - List outboundMappingType = refinedProjDef.getPasswordOutbound(); - if (outboundMappingType == null) { - return false; - } - for (MappingType mappingType: outboundMappingType) { - if (mappingType.getStrength() == MappingStrengthType.STRONG || mappingType.getStrength() == MappingStrengthType.WEAK) { - return true; - } - } - return false; - } - - public static boolean isPasswordReturnedByDefault(LensProjectionContext projCtx) { - CredentialsCapabilityType credentialsCapabilityType = ResourceTypeUtil.getEffectiveCapability(projCtx.getResource(), CredentialsCapabilityType.class); - return CapabilityUtil.isPasswordReturnedByDefault(credentialsCapabilityType); - } - - public static boolean evaluateBoolean(ExpressionType expressionBean, ExpressionVariables expressionVariables, - String contextDescription, ExpressionFactory expressionFactory, PrismContext prismContext, Task task, - OperationResult result) - throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - return evaluateExpressionSingle(expressionBean, expressionVariables, contextDescription, expressionFactory, prismContext, - task, result, - DOMUtil.XSD_BOOLEAN, false, null); - } - - public static String evaluateString(ExpressionType expressionBean, ExpressionVariables expressionVariables, - String contextDescription, ExpressionFactory expressionFactory, PrismContext prismContext, Task task, - OperationResult result) - throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - return evaluateExpressionSingle(expressionBean, expressionVariables, contextDescription, expressionFactory, prismContext, - task, result, - DOMUtil.XSD_STRING, null, null); - } - - public static LocalizableMessageType evaluateLocalizableMessageType(ExpressionType expressionBean, ExpressionVariables expressionVariables, - String contextDescription, ExpressionFactory expressionFactory, PrismContext prismContext, Task task, - OperationResult result) - throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - Function additionalConvertor = (o) -> { - if (o == null || o instanceof LocalizableMessageType) { - return o; - } else if (o instanceof LocalizableMessage) { - return LocalizationUtil.createLocalizableMessageType((LocalizableMessage) o); - } else { - return new SingleLocalizableMessageType().fallbackMessage(String.valueOf(o)); - } - }; - return evaluateExpressionSingle(expressionBean, expressionVariables, contextDescription, expressionFactory, prismContext, - task, result, LocalizableMessageType.COMPLEX_TYPE, null, additionalConvertor); - } - - public static T evaluateExpressionSingle(ExpressionType expressionBean, ExpressionVariables expressionVariables, - String contextDescription, ExpressionFactory expressionFactory, PrismContext prismContext, Task task, - OperationResult result, QName typeName, - T defaultValue, Function additionalConvertor) - throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - PrismPropertyDefinition resultDef = prismContext.definitionFactory().createPropertyDefinition( - new QName(SchemaConstants.NS_C, "result"), typeName); - Expression,PrismPropertyDefinition> expression = - expressionFactory.makeExpression(expressionBean, resultDef, MiscSchemaUtil.getExpressionProfile(), contextDescription, task, result); - ExpressionEvaluationContext eeContext = new ExpressionEvaluationContext(null, expressionVariables, contextDescription, task); - eeContext.setAdditionalConvertor(additionalConvertor); - PrismValueDeltaSetTriple> exprResultTriple = ModelExpressionThreadLocalHolder - .evaluateExpressionInContext(expression, eeContext, task, result); - List results = exprResultTriple.getZeroSet().stream() - .map(ppv -> (T) ppv.getRealValue()) - .collect(Collectors.toList()); - return getSingleValue(results, defaultValue, contextDescription); - } - - @NotNull - public static SingleLocalizableMessageType interpretLocalizableMessageTemplate(LocalizableMessageTemplateType template, - ExpressionVariables var, ExpressionFactory expressionFactory, PrismContext prismContext, - Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, - ConfigurationException, SecurityViolationException { - SingleLocalizableMessageType rv = new SingleLocalizableMessageType(); - if (template.getKey() != null) { - rv.setKey(template.getKey()); - } else if (template.getKeyExpression() != null) { - rv.setKey(evaluateString(template.getKeyExpression(), var, "localizable message key expression", expressionFactory, prismContext, task, result)); - } - if (!template.getArgument().isEmpty() && !template.getArgumentExpression().isEmpty()) { - throw new IllegalArgumentException("Both argument and argumentExpression items are non empty"); - } else if (!template.getArgumentExpression().isEmpty()) { - for (ExpressionType argumentExpression : template.getArgumentExpression()) { - LocalizableMessageType argument = evaluateLocalizableMessageType(argumentExpression, var, - "localizable message argument expression", expressionFactory, prismContext, task, result); - rv.getArgument().add(new LocalizableMessageArgumentType().localizable(argument)); - } - } else { - // TODO allow localizable messages templates here - rv.getArgument().addAll(template.getArgument()); - } - if (template.getFallbackMessage() != null) { - rv.setFallbackMessage(template.getFallbackMessage()); - } else if (template.getFallbackMessageExpression() != null) { - rv.setFallbackMessage(evaluateString(template.getFallbackMessageExpression(), var, - "localizable message fallback expression", expressionFactory, prismContext, task, result)); - } - return rv; - } - - public static void reclaimSequences(LensContext context, RepositoryService repositoryService, Task task, OperationResult result) throws SchemaException { - if (context == null) { - return; - } - - Map sequenceMap = context.getSequences(); - LOGGER.trace("Context sequence map: {}", sequenceMap); - for (Map.Entry sequenceMapEntry: sequenceMap.entrySet()) { - Collection unusedValues = new ArrayList<>(1); - unusedValues.add(sequenceMapEntry.getValue()); - try { - LOGGER.trace("Returning value {} to sequence {}", sequenceMapEntry.getValue(), sequenceMapEntry.getKey()); - repositoryService.returnUnusedValuesToSequence(sequenceMapEntry.getKey(), unusedValues, result); - } catch (ObjectNotFoundException e) { - LOGGER.error("Cannot return unused value to sequence {}: it does not exist", sequenceMapEntry.getKey(), e); - // ... but otherwise ignore it and go on - } - } - } - - public static void applyObjectPolicyConstraints(LensFocusContext focusContext, ArchetypePolicyType archetypePolicy, PrismContext prismContext) throws SchemaException, ConfigurationException { - if (archetypePolicy == null) { - return; - } - - final PrismObject focusNew = focusContext.getObjectNew(); - if (focusNew == null) { - // This is delete. Nothing to do. - return; - } - - for (ItemConstraintType itemConstraintType : archetypePolicy.getItemConstraint()) { - applyObjectPolicyItemConstraint(focusContext, archetypePolicy, prismContext, focusNew, itemConstraintType); - } - // Deprecated - for (ItemConstraintType itemConstraintType : archetypePolicy.getPropertyConstraint()) { - applyObjectPolicyItemConstraint(focusContext, archetypePolicy, prismContext, focusNew, itemConstraintType); - } - } - - private static void applyObjectPolicyItemConstraint(LensFocusContext focusContext, ArchetypePolicyType archetypePolicy, PrismContext prismContext, PrismObject focusNew, ItemConstraintType itemConstraintType) throws SchemaException, ConfigurationException { - if (itemConstraintType.getPath() == null) { - LOGGER.error("Invalid configuration. Path is mandatory for property constraint definition in {} defined in system configuration", archetypePolicy); - throw new SchemaException("Invalid configuration. Path is mandatory for property constraint definition in " + archetypePolicy + " defined in system configuration."); - } - ItemPath itemPath = itemConstraintType.getPath().getItemPath(); - if (BooleanUtils.isTrue(itemConstraintType.isOidBound())) { - PrismProperty prop = focusNew.findProperty(itemPath); - if (prop == null || prop.isEmpty()) { - String newValue = focusNew.getOid(); - if (newValue == null) { - newValue = OidUtil.generateOid(); - } - LOGGER.trace("Generating new OID-bound value for {}: {}", itemPath, newValue); - PrismObjectDefinition focusDefinition = focusContext.getObjectDefinition(); - PrismPropertyDefinition propDef = focusDefinition.findPropertyDefinition(itemPath); - if (propDef == null) { - throw new SchemaException("No definition for property "+itemPath+" in "+focusDefinition+" as specified in object policy"); - } - PropertyDelta propDelta = propDef.createEmptyDelta(itemPath); - if (String.class.isAssignableFrom(propDef.getTypeClass())) { - propDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(newValue, OriginType.USER_POLICY, null)); - } else if (PolyString.class.isAssignableFrom(propDef.getTypeClass())) { - propDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(new PolyString(newValue), OriginType.USER_POLICY, null)); - } else { - throw new SchemaException("Unsupported type "+propDef.getTypeName()+" for property "+itemPath+" in "+focusDefinition+" as specified in object policy, only string and polystring properties are supported for OID-bound mode"); - } - focusContext.swallowToSecondaryDelta(propDelta); - focusContext.recompute(); - } - } - } - - public static LensContext.ExportType getExportType(TraceType trace, OperationResult result) { - return result.isTracingNormal(trace.getClass()) ? LensContext.ExportType.TRACE : LensContext.ExportType.MINIMAL; - } - - public static LensContext.ExportType getExportTypeTraceOrReduced(TraceType trace, OperationResult result) { - return result.isTracingNormal(trace.getClass()) ? LensContext.ExportType.TRACE : LensContext.ExportType.REDUCED; - } - - public static ItemDelta getAprioriItemDelta(ObjectDelta focusDelta, ItemPath itemPath) { - return focusDelta != null ? focusDelta.findItemDelta(itemPath) : null; - } - - public static String determineExplicitArchetypeOid(PrismObject object) { - String explicitArchetypeOid = null; - // Used in cases where archetype assignment haven't had the change to be processed yet. - // E.g. in case that we are creating a new object with archetype assignment - if (object.canRepresent(AssignmentHolderType.class)) { - AssignmentHolderType assignmentHolderType = (AssignmentHolderType)object.asObjectable(); - List archetypeRefs = assignmentHolderType.getArchetypeRef(); - if (archetypeRefs.isEmpty()) { - explicitArchetypeOid = determineExplicitArchetypeOidFromAssignments(object); - } - } - return explicitArchetypeOid; - } - - public static String determineExplicitArchetypeOidFromAssignments(PrismObject object) { - String explicitArchetypeOid = null; - if (object.canRepresent(AssignmentHolderType.class)) { - for (AssignmentType assignment : ((AssignmentHolderType)object.asObjectable()).getAssignment()) { - ObjectReferenceType targetRef = assignment.getTargetRef(); - if (targetRef != null && QNameUtil.match(ArchetypeType.COMPLEX_TYPE, targetRef.getType())) { - explicitArchetypeOid = targetRef.getOid(); - } - } - } - return explicitArchetypeOid; - } -} +/* + * Copyright (c) 2010-2019 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; + +import java.util.*; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.common.refinery.RefinedResourceSchemaImpl; +import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule; +import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRuleTrigger; +import com.evolveum.midpoint.model.common.mapping.PrismValueDeltaSetTripleProducer; +import com.evolveum.midpoint.prism.path.ItemName; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.polystring.PolyString; +import com.evolveum.midpoint.prism.util.ItemDeltaItem; +import com.evolveum.midpoint.repo.api.RepositoryService; +import com.evolveum.midpoint.schema.SchemaConstantsGenerated; +import com.evolveum.midpoint.schema.util.*; +import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.LocalizableMessage; +import com.evolveum.midpoint.util.QNameUtil; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.LoggingUtils; + +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CredentialsCapabilityType; + +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang.BooleanUtils; + +import com.evolveum.midpoint.common.ActivationComputer; +import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; +import com.evolveum.midpoint.common.refinery.RefinedResourceSchema; +import com.evolveum.midpoint.model.common.mapping.MappingImpl; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.ItemDelta; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.delta.PrismValueDeltaSetTriple; +import com.evolveum.midpoint.prism.delta.PropertyDelta; +import com.evolveum.midpoint.provisioning.api.ProvisioningService; +import com.evolveum.midpoint.repo.common.ObjectResolver; +import com.evolveum.midpoint.repo.common.expression.Expression; +import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.repo.common.expression.Source; +import com.evolveum.midpoint.schema.CapabilityUtil; +import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; +import com.evolveum.midpoint.schema.ResultHandler; +import com.evolveum.midpoint.schema.SelectorOptions; +import com.evolveum.midpoint.schema.VirtualAssignmenetSpecification; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.DOMUtil; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; +import org.jetbrains.annotations.NotNull; + +import static com.evolveum.midpoint.util.MiscUtil.getSingleValue; +import static java.util.Collections.emptySet; + +/** + * @author semancik + * + */ +public class LensUtil { + + private static final Trace LOGGER = TraceManager.getTrace(LensUtil.class); + + public static ResourceType getResourceReadOnly(LensContext context, + String resourceOid, ProvisioningService provisioningService, Task task, OperationResult result) throws ObjectNotFoundException, + CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + ResourceType resourceType = context.getResource(resourceOid); + if (resourceType == null) { + // Fetching from provisioning to take advantage of caching and + // pre-parsed schema + Collection> options = SelectorOptions.createCollection(GetOperationOptions.createReadOnly()); + resourceType = provisioningService.getObject(ResourceType.class, resourceOid, options, task, result) + .asObjectable(); + context.rememberResource(resourceType); + } + return resourceType; + } + + @NotNull + static ResourceType getResourceReadOnly(LensContext context, String resourceOid, ObjectResolver objectResolver, + Task task, OperationResult result) throws ObjectNotFoundException, + CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + ResourceType resourceFromContext = context.getResource(resourceOid); + if (resourceFromContext != null) { + return resourceFromContext; + } else { + Collection> options = SelectorOptions.createCollection(GetOperationOptions.createReadOnly()); + ResourceType resourceFetched = objectResolver.getObject(ResourceType.class, resourceOid, options, task, result); + context.rememberResource(resourceFetched); + return resourceFetched; + } + } + + public static String refineProjectionIntent(ShadowKindType kind, String intent, ResourceType resource, PrismContext prismContext) throws SchemaException { + RefinedResourceSchema refinedSchema = RefinedResourceSchemaImpl.getRefinedSchema(resource, LayerType.MODEL, prismContext); + RefinedObjectClassDefinition rObjClassDef = refinedSchema.getRefinedDefinition(kind, intent); + if (rObjClassDef == null) { + LOGGER.error("No projection definition for kind={}, intent={} in {}", kind, intent, resource); + LOGGER.error("Diagnostic output follows:\n\nResource:\n{}\n\nRefined resource schema:\n{}", + resource.asPrismObject().debugDump(), refinedSchema.debugDump()); + throw new SchemaException("No projection definition for kind="+kind+" intent="+intent+" in "+resource); + } + return rObjClassDef.getIntent(); + } + + public static LensProjectionContext getProjectionContext(LensContext context, PrismObject equivalentAccount, + ProvisioningService provisioningService, PrismContext prismContext, + Task task, OperationResult result) throws ObjectNotFoundException, + CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + ShadowType equivalentAccountType = equivalentAccount.asObjectable(); + ShadowKindType kind = ShadowUtil.getKind(equivalentAccountType); + return getProjectionContext(context, ShadowUtil.getResourceOid(equivalentAccountType), + kind, equivalentAccountType.getIntent(), equivalentAccountType.getTag(), provisioningService, + prismContext, task, result); + } + + private static LensProjectionContext getProjectionContext(LensContext context, String resourceOid, + ShadowKindType kind, String intent, String tag, + ProvisioningService provisioningService, PrismContext prismContext, + Task task, OperationResult result) throws ObjectNotFoundException, + CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + ResourceType resource = getResourceReadOnly(context, resourceOid, provisioningService, task, result); + String refinedIntent = refineProjectionIntent(kind, intent, resource, prismContext); + ResourceShadowDiscriminator rsd = new ResourceShadowDiscriminator(resourceOid, kind, refinedIntent, tag, false); + return context.findProjectionContext(rsd); + } + + public static LensProjectionContext getOrCreateProjectionContext(LensContext context, + ResourceShadowDiscriminator rsd) { + LensProjectionContext accountSyncContext = context.findProjectionContext(rsd); + if (accountSyncContext == null) { + accountSyncContext = context.createProjectionContext(rsd); + ResourceType resource = context.getResource(rsd.getResourceOid()); + accountSyncContext.setResource(resource); + } + accountSyncContext.setDoReconciliation(context.isDoReconciliationForAllProjections()); + return accountSyncContext; + } + + public static LensProjectionContext createAccountContext(LensContext context, ResourceShadowDiscriminator rsd){ + return new LensProjectionContext(context, rsd); + } + + public static V cloneAndApplyMetadata(V value, boolean isAssignment, + Collection> origins) throws SchemaException { + return cloneAndApplyMetadata(value, isAssignment, () -> getAutoCreationIdentifier(origins)); + } + +// public static Collection cloneAndApplyMetadata(Collection values, boolean isAssignment, +// MappingType mapping) throws SchemaException { +// List rv = new ArrayList<>(); +// for (V value : values) { +// rv.add(cloneAndApplyMetadata(value, isAssignment, mapping::getName)); +// } +// return rv; +// } + + public static V cloneAndApplyMetadata(V value, boolean isAssignment, + PrismValueDeltaSetTripleProducer mapping) throws SchemaException { + return cloneAndApplyMetadata(value, isAssignment, mapping::getIdentifier); + } + + public static V cloneAndApplyMetadata(V value, boolean isAssignment, + MappingType mapping) throws SchemaException { + return cloneAndApplyMetadata(value, isAssignment, mapping::getName); + } + + private static V cloneAndApplyMetadata(V value, boolean isAssignment, + Supplier originMappingNameSupplier) throws SchemaException { + //noinspection unchecked + V cloned = (V) value.clone(); + if (isAssignment && cloned instanceof PrismContainerValue) { + ((PrismContainerValue) cloned).setId(null); + String originMappingName = originMappingNameSupplier.get(); + LOGGER.trace("cloneAndApplyMetadata: originMappingName = {}", originMappingName); + if (originMappingName != null) { + //noinspection unchecked + PrismContainer metadataContainer = ((PrismContainerValue) cloned).findOrCreateContainer(AssignmentType.F_METADATA); + metadataContainer.getValue().asContainerable().setOriginMappingName(originMappingName); + } + } + return cloned; + } + + private static String getAutoCreationIdentifier(Collection> origins) { + // let's ignore conflicts (name1 vs name2, named vs unnamed) for now + for (ItemValueWithOrigin origin : origins) { + if (origin.getMapping() != null && origin.getMapping().getIdentifier() != null) { + return origin.getMapping().getIdentifier(); + } + } + return null; + } + + public static PropertyDelta createActivationTimestampDelta(ActivationStatusType status, + XMLGregorianCalendar now, + PrismContainerDefinition activationDefinition, OriginType origin, + PrismContext prismContext) { + ItemName timestampPropertyName; + if (status == null || status == ActivationStatusType.ENABLED) { + timestampPropertyName = ActivationType.F_ENABLE_TIMESTAMP; + } else if (status == ActivationStatusType.DISABLED) { + timestampPropertyName = ActivationType.F_DISABLE_TIMESTAMP; + } else if (status == ActivationStatusType.ARCHIVED) { + timestampPropertyName = ActivationType.F_ARCHIVE_TIMESTAMP; + } else { + throw new IllegalArgumentException("Unknown activation status "+status); + } + + PrismPropertyDefinition timestampDef = activationDefinition.findPropertyDefinition(timestampPropertyName); + PropertyDelta timestampDelta + = timestampDef.createEmptyDelta(FocusType.F_ACTIVATION.append(timestampPropertyName)); + timestampDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(now, origin, null)); + return timestampDelta; + } + + public static void moveTriggers(LensProjectionContext projCtx, LensFocusContext focusCtx) throws SchemaException { + ObjectDelta projSecondaryDelta = projCtx.getSecondaryDelta(); + if (projSecondaryDelta == null) { + return; + } + Collection modifications = projSecondaryDelta.getModifications(); + Iterator iterator = modifications.iterator(); + while (iterator.hasNext()) { + ItemDelta projModification = iterator.next(); + LOGGER.trace("MOD: {}\n{}", projModification.getPath(), projModification.debugDumpLazily()); + if (projModification.getPath().equivalent(SchemaConstants.PATH_TRIGGER)) { + focusCtx.swallowToProjectionWaveSecondaryDelta(projModification); + iterator.remove(); + } + } + } + + public static Object getIterationVariableValue(LensProjectionContext accCtx) { + Integer iterationOld = null; + PrismObject shadowCurrent = accCtx.getObjectCurrent(); + if (shadowCurrent != null) { + iterationOld = shadowCurrent.asObjectable().getIteration(); + } + if (iterationOld == null) { + return accCtx.getIteration(); + } + PrismPropertyDefinition propDef = accCtx.getPrismContext().definitionFactory().createPropertyDefinition(ExpressionConstants.VAR_ITERATION_QNAME, + DOMUtil.XSD_INT); + PrismProperty propOld = propDef.instantiate(); + propOld.setRealValue(iterationOld); + PropertyDelta propDelta = propDef.createEmptyDelta(ExpressionConstants.VAR_ITERATION_QNAME); + propDelta.setRealValuesToReplace(accCtx.getIteration()); + PrismProperty propNew = propDef.instantiate(); + propNew.setRealValue(accCtx.getIteration()); + ItemDeltaItem,PrismPropertyDefinition> idi = new ItemDeltaItem<>(propOld, propDelta, propNew, propDef); + return idi; + } + + public static Object getIterationTokenVariableValue(LensProjectionContext accCtx) { + String iterationTokenOld = null; + PrismObject shadowCurrent = accCtx.getObjectCurrent(); + if (shadowCurrent != null) { + iterationTokenOld = shadowCurrent.asObjectable().getIterationToken(); + } + if (iterationTokenOld == null) { + return accCtx.getIterationToken(); + } + PrismPropertyDefinition propDef = accCtx.getPrismContext().definitionFactory().createPropertyDefinition( + ExpressionConstants.VAR_ITERATION_TOKEN_QNAME, DOMUtil.XSD_STRING); + PrismProperty propOld = propDef.instantiate(); + propOld.setRealValue(iterationTokenOld); + PropertyDelta propDelta = propDef.createEmptyDelta(ExpressionConstants.VAR_ITERATION_TOKEN_QNAME); + propDelta.setRealValuesToReplace(accCtx.getIterationToken()); + PrismProperty propNew = propDef.instantiate(); + propNew.setRealValue(accCtx.getIterationToken()); + ItemDeltaItem,PrismPropertyDefinition> idi = new ItemDeltaItem<>(propOld, propDelta, propNew, propDef); + return idi; + } + + /** + * Extracts the delta from this projection context and also from all other projection contexts that have + * equivalent discriminator. + */ + public static PropertyDelta findAPrioriDelta(LensContext context, + LensProjectionContext projCtx, ItemPath projectionPropertyPath) throws SchemaException { + PropertyDelta aPrioriDelta = null; + for (LensProjectionContext aProjCtx: findRelatedContexts(context, projCtx)) { + ObjectDelta aProjDelta = aProjCtx.getDelta(); + if (aProjDelta != null) { + PropertyDelta aPropProjDelta = aProjDelta.findPropertyDelta(projectionPropertyPath); + if (aPropProjDelta != null) { + if (aPrioriDelta == null) { + aPrioriDelta = aPropProjDelta.clone(); + } else { + aPrioriDelta.merge(aPropProjDelta); + } + } + } + } + return aPrioriDelta; + } + + /** + * Extracts the delta from this projection context and also from all other projection contexts that have + * equivalent discriminator. + */ + public static ObjectDelta findAPrioriDelta(LensContext context, + LensProjectionContext projCtx) throws SchemaException { + ObjectDelta aPrioriDelta = null; + for (LensProjectionContext aProjCtx: findRelatedContexts(context, projCtx)) { + ObjectDelta aProjDelta = aProjCtx.getDelta(); + if (aProjDelta != null) { + if (aPrioriDelta == null) { + aPrioriDelta = aProjDelta.clone(); + } else { + aPrioriDelta.merge(aProjDelta); + } + } + } + return aPrioriDelta; + } + + /** + * Returns a list of context that have equivalent discriminator with the reference context. Ordered by "order" in the + * discriminator. + */ + public static List findRelatedContexts( + LensContext context, LensProjectionContext refProjCtx) { + List projCtxs = new ArrayList<>(); + ResourceShadowDiscriminator refDiscr = refProjCtx.getResourceShadowDiscriminator(); + if (refDiscr == null) { + return projCtxs; + } + for (LensProjectionContext aProjCtx: context.getProjectionContexts()) { + ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator(); + if (refDiscr.equivalent(aDiscr)) { + projCtxs.add(aProjCtx); + } + } + Comparator orderComparator = new Comparator() { + @Override + public int compare(LensProjectionContext ctx1, LensProjectionContext ctx2) { + int order1 = ctx1.getResourceShadowDiscriminator().getOrder(); + int order2 = ctx2.getResourceShadowDiscriminator().getOrder(); + return Integer.compare(order1, order2); + } + }; + Collections.sort(projCtxs, orderComparator); + return projCtxs; + } + + public static boolean hasLowerOrderContext(LensContext context, + LensProjectionContext refProjCtx) { + ResourceShadowDiscriminator refDiscr = refProjCtx.getResourceShadowDiscriminator(); + for (LensProjectionContext aProjCtx: context.getProjectionContexts()) { + ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator(); + if (refDiscr.equivalent(aDiscr) && (refDiscr.getOrder() > aDiscr.getOrder())) { + return true; + } + } + return false; + } + + public static boolean hasDependentContext(LensContext context, + LensProjectionContext targetProjectionContext) { + for (LensProjectionContext projectionContext: context.getProjectionContexts()) { + for (ResourceObjectTypeDependencyType dependency: projectionContext.getDependencies()) { + if (isDependencyTargetContext(projectionContext, targetProjectionContext, dependency)) { + return true; + } + } + } + return false; + } + + public static boolean isDependencyTargetContext(LensProjectionContext sourceProjContext, LensProjectionContext targetProjectionContext, ResourceObjectTypeDependencyType dependency) { + ResourceShadowDiscriminator refDiscr = new ResourceShadowDiscriminator(dependency, + sourceProjContext.getResource().getOid(), sourceProjContext.getKind()); + return targetProjectionContext.compareResourceShadowDiscriminator(refDiscr, false); + } + + public static LensProjectionContext findLowerOrderContext(LensContext context, + LensProjectionContext refProjCtx) { + int minOrder = -1; + LensProjectionContext foundCtx = null; + ResourceShadowDiscriminator refDiscr = refProjCtx.getResourceShadowDiscriminator(); + for (LensProjectionContext aProjCtx: context.getProjectionContexts()) { + ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator(); + if (refDiscr.equivalent(aDiscr) && (refDiscr.getOrder() > aDiscr.getOrder())) { + if (minOrder < 0 || (aDiscr.getOrder() < minOrder)) { + minOrder = aDiscr.getOrder(); + foundCtx = aProjCtx; + } + } + } + return foundCtx; + } + + public static void setContextOid(LensContext context, + LensElementContext objectContext, String oid) { + objectContext.setOid(oid); + // Check if we need to propagate this oid also to higher-order contexts + if (!(objectContext instanceof LensProjectionContext)) { + return; + } + LensProjectionContext refProjCtx = (LensProjectionContext)objectContext; + ResourceShadowDiscriminator refDiscr = refProjCtx.getResourceShadowDiscriminator(); + if (refDiscr == null) { + return; + } + for (LensProjectionContext aProjCtx: context.getProjectionContexts()) { + ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator(); + if (aDiscr != null && refDiscr.equivalent(aDiscr) && (refDiscr.getOrder() < aDiscr.getOrder())) { + aProjCtx.setOid(oid); + } + } + } + + public static PrismObjectDefinition getFocusDefinition(LensContext context) { + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext == null) { + return null; + } + Class typeClass = focusContext.getObjectTypeClass(); + return context.getPrismContext().getSchemaRegistry().findObjectDefinitionByCompileTimeClass(typeClass); + } + + public static IterationSpecificationType getIterationSpecification(ObjectTemplateType objectTemplate) { + return objectTemplate != null ? objectTemplate.getIterationSpecification() : null; + } + + public static int determineMaxIterations(IterationSpecificationType iterationSpecType) { + return iterationSpecType != null ? iterationSpecType.getMaxIterations() : 0; + } + + public static String formatIterationToken(LensContext context, + LensElementContext accountContext, IterationSpecificationType iterationType, + int iteration, ExpressionFactory expressionFactory, ExpressionVariables variables, + Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + if (iterationType == null) { + return formatIterationTokenDefault(iteration); + } + ExpressionType tokenExpressionType = iterationType.getTokenExpression(); + if (tokenExpressionType == null) { + return formatIterationTokenDefault(iteration); + } + PrismContext prismContext = context.getPrismContext(); + PrismPropertyDefinition outputDefinition = prismContext.definitionFactory().createPropertyDefinition(ExpressionConstants.VAR_ITERATION_TOKEN_QNAME, + DOMUtil.XSD_STRING); + Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression(tokenExpressionType, outputDefinition, MiscSchemaUtil.getExpressionProfile(), "iteration token expression in "+accountContext.getHumanReadableName(), task, result); + + Collection> sources = new ArrayList<>(); + MutablePrismPropertyDefinition inputDefinition = prismContext.definitionFactory().createPropertyDefinition(ExpressionConstants.VAR_ITERATION_QNAME, + DOMUtil.XSD_INT); + inputDefinition.setMaxOccurs(1); + PrismProperty input = inputDefinition.instantiate(); + input.addRealValue(iteration); + ItemDeltaItem,PrismPropertyDefinition> idi = new ItemDeltaItem<>(input); + Source,PrismPropertyDefinition> iterationSource = new Source<>(idi, ExpressionConstants.VAR_ITERATION_QNAME); + sources.add(iterationSource); + + ExpressionEvaluationContext expressionContext = new ExpressionEvaluationContext(sources , variables, + "iteration token expression in "+accountContext.getHumanReadableName(), task); + PrismValueDeltaSetTriple> outputTriple = ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, expressionContext, task, result); + Collection> outputValues = outputTriple.getNonNegativeValues(); + if (outputValues.isEmpty()) { + return ""; + } + if (outputValues.size() > 1) { + throw new ExpressionEvaluationException("Iteration token expression in "+accountContext.getHumanReadableName()+" returned more than one value ("+outputValues.size()+" values)"); + } + String realValue = outputValues.iterator().next().getValue(); + if (realValue == null) { + return ""; + } + return realValue; + } + + public static String formatIterationTokenDefault(int iteration) { + if (iteration == 0) { + return ""; + } + return Integer.toString(iteration); + } + + public static boolean evaluateIterationCondition(LensContext context, + LensElementContext accountContext, IterationSpecificationType iterationSpecification, + int iteration, String iterationToken, boolean beforeIteration, + ExpressionFactory expressionFactory, ExpressionVariables variables, Task task, OperationResult result) + throws ExpressionEvaluationException, SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + if (iterationSpecification == null) { + return true; + } + ExpressionType expressionType; + String desc; + if (beforeIteration) { + expressionType = iterationSpecification.getPreIterationCondition(); + desc = "pre-iteration expression in "+accountContext.getHumanReadableName(); + } else { + expressionType = iterationSpecification.getPostIterationCondition(); + desc = "post-iteration expression in "+accountContext.getHumanReadableName(); + } + if (expressionType == null) { + return true; + } + Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression( + expressionType, ExpressionUtil.createConditionOutputDefinition(context.getPrismContext()), MiscSchemaUtil.getExpressionProfile(), + desc, task, result); + + variables.put(ExpressionConstants.VAR_ITERATION, iteration, Integer.class); + variables.put(ExpressionConstants.VAR_ITERATION_TOKEN, iterationToken, String.class); + + ExpressionEvaluationContext expressionContext = new ExpressionEvaluationContext(null , variables, desc, task); + ExpressionEnvironment env = new ExpressionEnvironment<>(context, null, task, result); + PrismValueDeltaSetTriple> outputTriple = + ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, expressionContext, env, result); + Collection> outputValues = outputTriple.getNonNegativeValues(); + if (outputValues.isEmpty()) { + return false; + } + if (outputValues.size() > 1) { + throw new ExpressionEvaluationException(desc+" returned more than one value ("+outputValues.size()+" values)"); + } + Boolean realValue = outputValues.iterator().next().getValue(); + if (realValue == null) { + return false; + } + return realValue; + + } + + /** + * Used for assignments and similar objects that do not have separate lifecycle. + */ + public static boolean isAssignmentValid(AssignmentHolderType focus, AssignmentType assignment, XMLGregorianCalendar now, + ActivationComputer activationComputer, LifecycleStateModelType focusStateModel) { + ObjectReferenceType targetRef = assignment.getTargetRef(); + if (targetRef != null && QNameUtil.match(ArchetypeType.COMPLEX_TYPE, targetRef.getType())) { + // Archetype assignments are always valid, even in non-valid lifecycle states. + // The object cannot lose its (arche)type. + return true; + } + String focusLifecycleState = focus.getLifecycleState(); + + if (!activationComputer.lifecycleHasActiveAssignments(focusLifecycleState, focusStateModel)) { + return false; + } + return isValid(assignment.getLifecycleState(), assignment.getActivation(), now, activationComputer, focusStateModel); + } + + public static Collection getForcedAssignments(LifecycleStateModelType lifecycleModel, String targetLifecycle, + ObjectResolver objectResolver, PrismContext prismContext, Task task, OperationResult result) throws SchemaException, + ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { + VirtualAssignmenetSpecification virtualAssignmenetSpecification = LifecycleUtil.getForcedAssignmentSpecification(lifecycleModel, targetLifecycle, prismContext); + + Collection forcedAssignments = new HashSet<>(); + if (virtualAssignmenetSpecification != null) { + + ResultHandler handler = (object, parentResult) -> { + AssignmentType assignment = ObjectTypeUtil.createAssignmentTo(object, prismContext); + return forcedAssignments.add(assignment); + }; + + objectResolver.searchIterative(virtualAssignmenetSpecification.getType(), + prismContext.queryFactory().createQuery(virtualAssignmenetSpecification.getFilter()), null, handler, task, result); + + } + + return forcedAssignments; + } + + public static boolean isFocusValid(AssignmentHolderType focus, XMLGregorianCalendar now, ActivationComputer activationComputer, LifecycleStateModelType focusStateModel) { + if (FocusType.class.isAssignableFrom(focus.getClass())) { + return isValid(focus.getLifecycleState(), ((FocusType) focus).getActivation(), now, activationComputer, focusStateModel); + } + return isValid(focus.getLifecycleState(), null, now, activationComputer, focusStateModel); + } + + private static boolean isValid(String lifecycleState, ActivationType activationType, XMLGregorianCalendar now, ActivationComputer activationComputer, LifecycleStateModelType focusStateModel) { + TimeIntervalStatusType validityStatus = activationComputer.getValidityStatus(activationType, now); + ActivationStatusType effectiveStatus = activationComputer.getEffectiveStatus(lifecycleState, activationType, validityStatus, focusStateModel); + return effectiveStatus == ActivationStatusType.ENABLED; + } + + public static AssignmentPathVariables computeAssignmentPathVariables(AssignmentPathImpl assignmentPath) throws SchemaException { + if (assignmentPath == null || assignmentPath.isEmpty()) { + return null; + } + AssignmentPathVariables vars = new AssignmentPathVariables(); + vars.setAssignmentPath(assignmentPath.clone()); + + Iterator iterator = assignmentPath.getSegments().iterator(); + while (iterator.hasNext()) { + AssignmentPathSegmentImpl segment = iterator.next(); + ItemDeltaItem,PrismContainerDefinition> segmentAssignmentIdi = segment.getAssignmentIdi(); + + ItemDeltaItem,PrismContainerDefinition> magicAssignmentIdi; + // Magic assignment + if (vars.getMagicAssignment() == null) { + magicAssignmentIdi = segmentAssignmentIdi.clone(); + vars.setMagicAssignment(magicAssignmentIdi); + } else { + // Collect extension values from the assignment extension + magicAssignmentIdi = vars.getMagicAssignment(); + mergeExtension(magicAssignmentIdi, segmentAssignmentIdi); + } + + // Collect extension values from the source object extension + ObjectType segmentSource = segment.getSource(); + if (segmentSource != null) { + mergeExtension(magicAssignmentIdi, segmentSource.asPrismObject()); + } + + // immediate assignment (use assignment from previous iteration) + vars.setImmediateAssignment(vars.getThisAssignment()); + + // this assignment + ItemDeltaItem,PrismContainerDefinition> thisAssignment = segmentAssignmentIdi.clone(); + vars.setThisAssignment(thisAssignment); + + if (iterator.hasNext() && segmentSource instanceof AbstractRoleType) { + vars.setImmediateRole((PrismObject) segmentSource.asPrismObject()); + } + } + + AssignmentPathSegmentImpl focusAssignmentSegment = assignmentPath.first(); + vars.setFocusAssignment(focusAssignmentSegment.getAssignmentIdi().clone()); + + // a bit of hack -- TODO reconsider in 3.7 + // objects are already cloned + convertToLegacy(vars.getMagicAssignment()); + convertToLegacy(vars.getThisAssignment()); + convertToLegacy(vars.getFocusAssignment()); + convertToLegacy(vars.getImmediateAssignment()); + + return vars; + } + + private static void convertToLegacy( + ItemDeltaItem, PrismContainerDefinition> idi) { + if (idi == null || idi.getDelta() == null || idi.getSubItemDeltas() != null) { + return; + } + // Legacy approach (when adding/removing assignments) was: itemOld+itemNew = value, delta = null + // This was recently changed, to provide precise information (add = null->itemNew, delete = itemOld->null). + // However, to not break scripts before 3.6 release we provide imitation of old behavior here. + // (Moreover, for magic assignment the delta is not correct anyway.) + if (idi.getDelta().isAdd() || idi.getDelta().isReplace()) { + idi.setItemOld(idi.getItemNew().clone()); + } else { + idi.setItemNew(idi.getItemOld().clone()); + } + idi.setDelta(null); + } + + private static void mergeExtension(ItemDeltaItem,PrismContainerDefinition> destIdi, ItemDeltaItem,PrismContainerDefinition> srcIdi) throws SchemaException { + mergeExtension(destIdi.getItemOld(), srcIdi.getItemOld()); + mergeExtension(destIdi.getItemNew(), srcIdi.getItemNew()); + if (srcIdi.getDelta() != null || srcIdi.getSubItemDeltas() != null) { + throw new UnsupportedOperationException("Merge of IDI with deltas not supported"); + } + } + + private static void mergeExtension(Item,PrismContainerDefinition> dstItem, + Item,PrismContainerDefinition> srcItem) throws SchemaException { + if (srcItem == null || dstItem == null) { + return; + } + PrismContainer srcExtension = ((PrismContainer)srcItem).findContainer(AssignmentType.F_EXTENSION); + mergeExtensionContainers(dstItem, srcExtension); + } + + private static void mergeExtension(ItemDeltaItem,PrismContainerDefinition> destIdi, + PrismObject srcObject) throws SchemaException { + if (srcObject == null) { + return; + } + + PrismContainer srcExtension = srcObject.findContainer(ObjectType.F_EXTENSION); + + mergeExtensionContainers(destIdi.getItemNew(), srcExtension); + mergeExtensionContainers(destIdi.getItemOld(), srcExtension); + } + + private static void mergeExtensionContainers(Item,PrismContainerDefinition> dstItem, PrismContainer srcExtension) throws SchemaException { + if (dstItem == null) { + return; + } + PrismContainer dstContainer = (PrismContainer) dstItem; + if (srcExtension != null && !srcExtension.isEmpty()) { + PrismContainer dstExtensionContainer = dstContainer.findOrCreateContainer(AssignmentType.F_EXTENSION); + PrismContainerValue dstExtensionContainerValue = dstExtensionContainer.getValues().isEmpty() + ? dstExtensionContainer.createNewValue() : dstExtensionContainer.getValue(); + ObjectTypeUtil.mergeExtension(dstExtensionContainerValue, srcExtension.getValue()); + } + } + + public static MappingImpl.Builder addAssignmentPathVariables(MappingImpl.Builder builder, AssignmentPathVariables assignmentPathVariables, PrismContext prismContext) { + ExpressionVariables expressionVariables = new ExpressionVariables(); + ModelImplUtils.addAssignmentPathVariables(assignmentPathVariables, expressionVariables, prismContext); + return builder.addVariableDefinitions(expressionVariables); + } + + public static void checkContextSanity(LensContext context, String activityDescription, + OperationResult result) throws SchemaException, PolicyViolationException { + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext != null) { + PrismObject focusObjectNew = focusContext.getObjectNew(); + if (focusObjectNew != null) { + PolyStringType namePolyType = focusObjectNew.asObjectable().getName(); + if (namePolyType == null) { + throw new SchemaException("Focus "+focusObjectNew+" does not have a name after "+activityDescription); + } + ArchetypePolicyType archetypePolicy = focusContext.getArchetypePolicyType(); + checkArchetypePolicy(focusContext, archetypePolicy); + } + } + } + + private static void checkArchetypePolicy(LensFocusContext focusContext, ArchetypePolicyType archetypePolicy) throws SchemaException, PolicyViolationException { + if (archetypePolicy == null) { + return; + } + PrismObject focusObjectNew = focusContext.getObjectNew(); + ObjectDelta focusDelta = focusContext.getDelta(); + + for (ItemConstraintType itemConstraintType : archetypePolicy.getItemConstraint()) { + processItemConstraint(focusContext, focusDelta, focusObjectNew, itemConstraintType); + } + // Deprecated + for (ItemConstraintType itemConstraintType : archetypePolicy.getPropertyConstraint()) { + processItemConstraint(focusContext, focusDelta, focusObjectNew, itemConstraintType); + } + + } + + private static void processItemConstraint(LensFocusContext focusContext, ObjectDelta focusDelta, PrismObject focusObjectNew, ItemConstraintType itemConstraintType) throws PolicyViolationException { + ItemPath itemPath = itemConstraintType.getPath().getItemPath(); + if (BooleanUtils.isTrue(itemConstraintType.isOidBound())) { + if (focusDelta != null) { + if (focusDelta.isAdd()) { + PrismProperty propNew = focusObjectNew.findProperty(itemPath); + if (propNew != null) { + // prop delta is OK, but it has to match + if (focusObjectNew.getOid() != null) { + if (!focusObjectNew.getOid().equals(propNew.getRealValue().toString())) { + throw new PolicyViolationException("Cannot set "+itemPath+" to a value different than OID in oid bound mode"); + } + } + } + } else { + PropertyDelta nameDelta = focusDelta.findPropertyDelta(itemPath); + if (nameDelta != null) { + if (nameDelta.isReplace()) { + Collection> valuesToReplace = nameDelta.getValuesToReplace(); + if (valuesToReplace.size() == 1) { + String stringValue = valuesToReplace.iterator().next().getValue().toString(); + if (focusContext.getOid().equals(stringValue)) { + // This is OK. It is most likely a correction made by a recompute. + return; + } + } + } + throw new PolicyViolationException("Cannot change "+itemPath+" in oid bound mode"); + } + } + } + } + + } + + public static PrismContainer createAssignmentSingleValueContainer(@NotNull AssignmentType assignmentType) throws SchemaException { + // Make it appear to be single-value. Therefore paths without segment IDs will work. + return assignmentType.asPrismContainerValue().asSingleValuedContainer(SchemaConstantsGenerated.C_ASSIGNMENT); + } + + public static AssignmentType getAssignmentType(ItemDeltaItem,PrismContainerDefinition> assignmentIdi, boolean old) { + return PrismContainerValue.asContainerable(assignmentIdi.getSingleValue(old)); + } + + + public static String getChannel(LensContext context, Task task) { + if (context != null && context.getChannel() != null){ + return context.getChannel(); + } else if (task.getChannel() != null){ + return task.getChannel(); + } + return null; + } + + public static void setDeltaOldValue(LensElementContext ctx, ItemDelta itemDelta) { + if (itemDelta.getEstimatedOldValues() != null) { + return; + } + if (ctx.getObjectOld() == null) { + return; + } + Item itemOld = ctx.getObjectOld().findItem(itemDelta.getPath()); + if (itemOld != null) { + //noinspection unchecked + itemDelta.setEstimatedOldValues((Collection) PrismValueCollectionsUtil.cloneCollection(itemOld.getValues())); + return; + } + // Here we need to distinguish whether the item is missing because it is not filled in (e.g. familyName in MID-4237) + // or because it was not loaded (as for attributes or associations). + if (!isItemLoadable(ctx.getObjectOld(), itemDelta.getPath())) { + itemDelta.setEstimatedOldValues(emptySet()); + return; + } + // get the old data from current object. Still better estimate than nothing + if (ctx.getObjectCurrent() != null) { + itemOld = ctx.getObjectCurrent().findItem(itemDelta.getPath()); + if (itemOld != null) { + //noinspection unchecked + itemDelta.setEstimatedOldValues((Collection) PrismValueCollectionsUtil.cloneCollection(itemOld.getValues())); + } + } + } + + // a heuristic by now + private static boolean isItemLoadable(PrismObject object, ItemPath path) { + if (!(object.asObjectable() instanceof ShadowType)) { + return false; + } + return path.startsWithName(ShadowType.F_ATTRIBUTES) || path.startsWithName(ShadowType.F_ASSOCIATION); + } + + public static void setDeltaOldValue(LensElementContext ctx, ObjectDelta objectDelta) { + if (objectDelta == null) { + return; + } + if (!objectDelta.isModify()) { + return; + } + for (ItemDelta modification: objectDelta.getModifications()) { + setDeltaOldValue(ctx, modification); + } + } + + public static LensObjectDeltaOperation createObjectDeltaOperation(ObjectDelta focusDelta, OperationResult result, + LensElementContext focusContext, LensProjectionContext projCtx) { + return createObjectDeltaOperation(focusDelta, result, focusContext, projCtx, null); + } + + // projCtx may or may not be present (object itself can be focus or projection) + public static LensObjectDeltaOperation createObjectDeltaOperation(ObjectDelta objectDelta, OperationResult result, + LensElementContext objectContext, + LensProjectionContext projCtx, + ResourceType resource) { + LensObjectDeltaOperation objectDeltaOp = new LensObjectDeltaOperation<>(objectDelta.clone()); + objectDeltaOp.setExecutionResult(result); + PrismObject object = objectContext.getObjectAny(); + if (object != null) { + PolyString name = object.getName(); + if (name == null && object.asObjectable() instanceof ShadowType) { + try { + name = ShadowUtil.determineShadowName((PrismObject) object); + if (name == null) { + LOGGER.debug("No name for shadow:\n{}", object.debugDump()); + } else if (name.getNorm() == null) { + name.recompute(objectContext.getPrismContext().getDefaultPolyStringNormalizer()); + } + } catch (SchemaException e) { + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't determine name for shadow -- continuing with no name; shadow:\n{}", e, object.debugDump()); + } + } + objectDeltaOp.setObjectName(name); + } + if (resource == null && projCtx != null) { + resource = projCtx.getResource(); + } + if (resource != null) { + objectDeltaOp.setResourceOid(resource.getOid()); + objectDeltaOp.setResourceName(PolyString.toPolyString(resource.getName())); + } else if (objectContext instanceof LensProjectionContext) { + objectDeltaOp.setResourceOid(((LensProjectionContext) objectContext).getResourceOid()); + } + return objectDeltaOp; + } + + public static void triggerRule(@NotNull EvaluatedPolicyRule rule, Collection> triggers, + Collection policySituations) { + + LOGGER.debug("Policy rule {} triggered: {}", rule.getName(), triggers); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Policy rule {} triggered:\n{}", rule.getName(), DebugUtil.debugDump(triggers, 1)); + } + + ((EvaluatedPolicyRuleImpl) rule).addTriggers(triggers); + CollectionUtils.addIgnoreNull(policySituations, rule.getPolicySituation()); + } + + public static void processRuleWithException(@NotNull EvaluatedPolicyRule rule, Collection> triggers, + PolicyExceptionType policyException) { + + LOGGER.debug("Policy rule {} would be triggered, but there is an exception for it. Not triggering", rule.getName()); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Policy rule {} would be triggered, but there is an exception for it:\nTriggers:\n{}\nException:\n{}", + rule.getName(), DebugUtil.debugDump(triggers, 1), policyException); + } + ((EvaluatedPolicyRuleImpl)rule).addPolicyException(policyException); + } + + + public static void checkMaxIterations(int iteration, int maxIterations, String conflictMessage, String humanReadableName) + throws ObjectAlreadyExistsException { + if (iteration > maxIterations) { + StringBuilder sb = new StringBuilder(); + if (iteration == 1) { + sb.append("Error processing "); + } else { + sb.append("Too many iterations (").append(iteration).append(") for "); + } + sb.append(humanReadableName); + if (iteration == 1) { + sb.append(": constraint violation: "); + } else { + sb.append(": cannot determine values that satisfy constraints: "); + } + if (conflictMessage != null) { + sb.append(conflictMessage); + } + throw new ObjectAlreadyExistsException(sb.toString()); + } + } + + public static boolean needsFullShadowForCredentialProcessing(LensProjectionContext projCtx) throws SchemaException { + RefinedObjectClassDefinition refinedProjDef = projCtx.getStructuralObjectClassDefinition(); + if (refinedProjDef == null) { + return false; + } + + List outboundMappingType = refinedProjDef.getPasswordOutbound(); + if (outboundMappingType == null) { + return false; + } + for (MappingType mappingType: outboundMappingType) { + if (mappingType.getStrength() == MappingStrengthType.STRONG || mappingType.getStrength() == MappingStrengthType.WEAK) { + return true; + } + } + return false; + } + + public static boolean isPasswordReturnedByDefault(LensProjectionContext projCtx) { + CredentialsCapabilityType credentialsCapabilityType = ResourceTypeUtil.getEffectiveCapability(projCtx.getResource(), CredentialsCapabilityType.class); + return CapabilityUtil.isPasswordReturnedByDefault(credentialsCapabilityType); + } + + public static boolean evaluateBoolean(ExpressionType expressionBean, ExpressionVariables expressionVariables, + String contextDescription, ExpressionFactory expressionFactory, PrismContext prismContext, Task task, + OperationResult result) + throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + return evaluateExpressionSingle(expressionBean, expressionVariables, contextDescription, expressionFactory, prismContext, + task, result, + DOMUtil.XSD_BOOLEAN, false, null); + } + + public static String evaluateString(ExpressionType expressionBean, ExpressionVariables expressionVariables, + String contextDescription, ExpressionFactory expressionFactory, PrismContext prismContext, Task task, + OperationResult result) + throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + return evaluateExpressionSingle(expressionBean, expressionVariables, contextDescription, expressionFactory, prismContext, + task, result, + DOMUtil.XSD_STRING, null, null); + } + + public static LocalizableMessageType evaluateLocalizableMessageType(ExpressionType expressionBean, ExpressionVariables expressionVariables, + String contextDescription, ExpressionFactory expressionFactory, PrismContext prismContext, Task task, + OperationResult result) + throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + Function additionalConvertor = (o) -> { + if (o == null || o instanceof LocalizableMessageType) { + return o; + } else if (o instanceof LocalizableMessage) { + return LocalizationUtil.createLocalizableMessageType((LocalizableMessage) o); + } else { + return new SingleLocalizableMessageType().fallbackMessage(String.valueOf(o)); + } + }; + return evaluateExpressionSingle(expressionBean, expressionVariables, contextDescription, expressionFactory, prismContext, + task, result, LocalizableMessageType.COMPLEX_TYPE, null, additionalConvertor); + } + + public static T evaluateExpressionSingle(ExpressionType expressionBean, ExpressionVariables expressionVariables, + String contextDescription, ExpressionFactory expressionFactory, PrismContext prismContext, Task task, + OperationResult result, QName typeName, + T defaultValue, Function additionalConvertor) + throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { + PrismPropertyDefinition resultDef = prismContext.definitionFactory().createPropertyDefinition( + new QName(SchemaConstants.NS_C, "result"), typeName); + Expression,PrismPropertyDefinition> expression = + expressionFactory.makeExpression(expressionBean, resultDef, MiscSchemaUtil.getExpressionProfile(), contextDescription, task, result); + ExpressionEvaluationContext eeContext = new ExpressionEvaluationContext(null, expressionVariables, contextDescription, task); + eeContext.setAdditionalConvertor(additionalConvertor); + PrismValueDeltaSetTriple> exprResultTriple = ModelExpressionThreadLocalHolder + .evaluateExpressionInContext(expression, eeContext, task, result); + List results = exprResultTriple.getZeroSet().stream() + .map(ppv -> (T) ppv.getRealValue()) + .collect(Collectors.toList()); + return getSingleValue(results, defaultValue, contextDescription); + } + + @NotNull + public static SingleLocalizableMessageType interpretLocalizableMessageTemplate(LocalizableMessageTemplateType template, + ExpressionVariables var, ExpressionFactory expressionFactory, PrismContext prismContext, + Task task, OperationResult result) + throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, CommunicationException, + ConfigurationException, SecurityViolationException { + SingleLocalizableMessageType rv = new SingleLocalizableMessageType(); + if (template.getKey() != null) { + rv.setKey(template.getKey()); + } else if (template.getKeyExpression() != null) { + rv.setKey(evaluateString(template.getKeyExpression(), var, "localizable message key expression", expressionFactory, prismContext, task, result)); + } + if (!template.getArgument().isEmpty() && !template.getArgumentExpression().isEmpty()) { + throw new IllegalArgumentException("Both argument and argumentExpression items are non empty"); + } else if (!template.getArgumentExpression().isEmpty()) { + for (ExpressionType argumentExpression : template.getArgumentExpression()) { + LocalizableMessageType argument = evaluateLocalizableMessageType(argumentExpression, var, + "localizable message argument expression", expressionFactory, prismContext, task, result); + rv.getArgument().add(new LocalizableMessageArgumentType().localizable(argument)); + } + } else { + // TODO allow localizable messages templates here + rv.getArgument().addAll(template.getArgument()); + } + if (template.getFallbackMessage() != null) { + rv.setFallbackMessage(template.getFallbackMessage()); + } else if (template.getFallbackMessageExpression() != null) { + rv.setFallbackMessage(evaluateString(template.getFallbackMessageExpression(), var, + "localizable message fallback expression", expressionFactory, prismContext, task, result)); + } + return rv; + } + + public static void reclaimSequences(LensContext context, RepositoryService repositoryService, Task task, OperationResult result) throws SchemaException { + if (context == null) { + return; + } + + Map sequenceMap = context.getSequences(); + LOGGER.trace("Context sequence map: {}", sequenceMap); + for (Map.Entry sequenceMapEntry: sequenceMap.entrySet()) { + Collection unusedValues = new ArrayList<>(1); + unusedValues.add(sequenceMapEntry.getValue()); + try { + LOGGER.trace("Returning value {} to sequence {}", sequenceMapEntry.getValue(), sequenceMapEntry.getKey()); + repositoryService.returnUnusedValuesToSequence(sequenceMapEntry.getKey(), unusedValues, result); + } catch (ObjectNotFoundException e) { + LOGGER.error("Cannot return unused value to sequence {}: it does not exist", sequenceMapEntry.getKey(), e); + // ... but otherwise ignore it and go on + } + } + } + + public static void applyObjectPolicyConstraints(LensFocusContext focusContext, ArchetypePolicyType archetypePolicy, PrismContext prismContext) throws SchemaException, ConfigurationException { + if (archetypePolicy == null) { + return; + } + + final PrismObject focusNew = focusContext.getObjectNew(); + if (focusNew == null) { + // This is delete. Nothing to do. + return; + } + + for (ItemConstraintType itemConstraintType : archetypePolicy.getItemConstraint()) { + applyObjectPolicyItemConstraint(focusContext, archetypePolicy, prismContext, focusNew, itemConstraintType); + } + // Deprecated + for (ItemConstraintType itemConstraintType : archetypePolicy.getPropertyConstraint()) { + applyObjectPolicyItemConstraint(focusContext, archetypePolicy, prismContext, focusNew, itemConstraintType); + } + } + + private static void applyObjectPolicyItemConstraint(LensFocusContext focusContext, ArchetypePolicyType archetypePolicy, PrismContext prismContext, PrismObject focusNew, ItemConstraintType itemConstraintType) throws SchemaException, ConfigurationException { + if (itemConstraintType.getPath() == null) { + LOGGER.error("Invalid configuration. Path is mandatory for property constraint definition in {} defined in system configuration", archetypePolicy); + throw new SchemaException("Invalid configuration. Path is mandatory for property constraint definition in " + archetypePolicy + " defined in system configuration."); + } + ItemPath itemPath = itemConstraintType.getPath().getItemPath(); + if (BooleanUtils.isTrue(itemConstraintType.isOidBound())) { + PrismProperty prop = focusNew.findProperty(itemPath); + if (prop == null || prop.isEmpty()) { + String newValue = focusNew.getOid(); + if (newValue == null) { + newValue = OidUtil.generateOid(); + } + LOGGER.trace("Generating new OID-bound value for {}: {}", itemPath, newValue); + PrismObjectDefinition focusDefinition = focusContext.getObjectDefinition(); + PrismPropertyDefinition propDef = focusDefinition.findPropertyDefinition(itemPath); + if (propDef == null) { + throw new SchemaException("No definition for property "+itemPath+" in "+focusDefinition+" as specified in object policy"); + } + PropertyDelta propDelta = propDef.createEmptyDelta(itemPath); + if (String.class.isAssignableFrom(propDef.getTypeClass())) { + propDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(newValue, OriginType.USER_POLICY, null)); + } else if (PolyString.class.isAssignableFrom(propDef.getTypeClass())) { + propDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(new PolyString(newValue), OriginType.USER_POLICY, null)); + } else { + throw new SchemaException("Unsupported type "+propDef.getTypeName()+" for property "+itemPath+" in "+focusDefinition+" as specified in object policy, only string and polystring properties are supported for OID-bound mode"); + } + focusContext.swallowToSecondaryDelta(propDelta); + focusContext.recompute(); + } + } + } + + public static LensContext.ExportType getExportType(TraceType trace, OperationResult result) { + return result.isTracingNormal(trace.getClass()) ? LensContext.ExportType.TRACE : LensContext.ExportType.MINIMAL; + } + + public static LensContext.ExportType getExportTypeTraceOrReduced(TraceType trace, OperationResult result) { + return result.isTracingNormal(trace.getClass()) ? LensContext.ExportType.TRACE : LensContext.ExportType.REDUCED; + } + + public static ItemDelta getAprioriItemDelta(ObjectDelta focusDelta, ItemPath itemPath) { + return focusDelta != null ? focusDelta.findItemDelta(itemPath) : null; + } + + public static String determineExplicitArchetypeOid(PrismObject object) { + String explicitArchetypeOid = null; + // Used in cases where archetype assignment haven't had the change to be processed yet. + // E.g. in case that we are creating a new object with archetype assignment + if (object.canRepresent(AssignmentHolderType.class)) { + AssignmentHolderType assignmentHolderType = (AssignmentHolderType)object.asObjectable(); + List archetypeRefs = assignmentHolderType.getArchetypeRef(); + if (archetypeRefs.isEmpty()) { + explicitArchetypeOid = determineExplicitArchetypeOidFromAssignments(object); + } + } + return explicitArchetypeOid; + } + + public static String determineExplicitArchetypeOidFromAssignments(PrismObject object) { + String explicitArchetypeOid = null; + if (object.canRepresent(AssignmentHolderType.class)) { + for (AssignmentType assignment : ((AssignmentHolderType)object.asObjectable()).getAssignment()) { + ObjectReferenceType targetRef = assignment.getTargetRef(); + if (targetRef != null && QNameUtil.match(ArchetypeType.COMPLEX_TYPE, targetRef.getType())) { + explicitArchetypeOid = targetRef.getOid(); + } + } + } + return explicitArchetypeOid; + } +} 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 06af7be080a..7afdf41d776 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 @@ -6,7 +6,10 @@ */ package com.evolveum.midpoint.model.impl.lens.projector; +import com.evolveum.midpoint.model.impl.lens.*; import com.evolveum.midpoint.model.impl.lens.projector.mappings.*; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; import com.evolveum.midpoint.prism.path.ItemName; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; @@ -14,10 +17,6 @@ import com.evolveum.midpoint.model.api.ModelExecuteOptions; import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; import com.evolveum.midpoint.model.api.expr.MidpointFunctions; -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; -import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; -import com.evolveum.midpoint.model.impl.lens.LensUtil; import com.evolveum.midpoint.model.api.context.SynchronizationIntent; import com.evolveum.midpoint.model.impl.util.ModelImplUtils; import com.evolveum.midpoint.prism.*; @@ -50,6 +49,7 @@ import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ActivationValidityCapabilityType; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -68,7 +68,8 @@ * @author Radovan Semancik */ @Component -public class ActivationProcessor { +@ProcessorExecution(focusRequired = true, focusType = FocusType.class) +public class ActivationProcessor implements ProjectorProcessor { private static final Trace LOGGER = TraceManager.getTrace(ActivationProcessor.class); @@ -77,15 +78,47 @@ public class ActivationProcessor { 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"); + private static final String OP_ACTIVATION = Projector.class.getName() + ".activation"; // for historical reasons + @Autowired private ContextLoader contextLoader; @Autowired private PrismContext prismContext; @Autowired private MappingEvaluator mappingEvaluator; @Autowired private MidpointFunctions midpointFunctions; + @Autowired private ClockworkMedic medic; private PrismObjectDefinition userDefinition; private PrismContainerDefinition activationDefinition; - public void processActivation(LensContext context, + // not a "medic-managed" entry point + void processActivationForAllResources(LensContext context, String activityDescription, + XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, + ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, + SecurityViolationException { + OperationResult activationResult = result.subresult(OP_ACTIVATION) + .setMinor() + .build(); + try { + LOGGER.trace("Processing activation for all contexts"); + for (LensProjectionContext projectionContext : context.getProjectionContexts()) { + if (projectionContext.getSynchronizationPolicyDecision() != SynchronizationPolicyDecision.BROKEN + && projectionContext.getSynchronizationPolicyDecision() != SynchronizationPolicyDecision.IGNORE) { + processActivation(context, projectionContext, now, task, activationResult); + projectionContext.recompute(); + } + } + context.removeIgnoredContexts(); + medic.traceContext(LOGGER, activityDescription, "projection activation of all resources", true, + context, true); + context.checkConsistenceIfNeeded(); + } catch (Throwable t) { + activationResult.recordFatalError(t); + throw t; + } finally { + activationResult.computeStatusIfUnknown(); + } + } + + private void processActivation(LensContext context, LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { LensFocusContext focusContext = context.getFocusContext(); @@ -844,52 +877,27 @@ private ItemDeltaItem,PrismPr PrismProperty existsPropOld = existsProp.clone(); existsPropOld.setRealValue(existsOld); PropertyDelta existsDelta = existsPropOld.createDelta(); + //noinspection unchecked existsDelta.setValuesToReplace(prismContext.itemFactory().createPropertyValue(existsNew)); return new ItemDeltaItem<>(existsPropOld, existsDelta, existsProp, existsDef); } } - @SuppressWarnings({ "unchecked", "rawtypes" }) - public void processLifecycle(LensContext context, LensProjectionContext projCtx, - XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext != null && !FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - // We can do this only for focal object. - LOGGER.trace("Skipping lifecycle evaluation because focus is not FocusType"); - return; - } - - processLifecycleFocus((LensContext)context, projCtx, now, task, result); - } - - private void processLifecycleFocus(LensContext context, LensProjectionContext projCtx, - XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - LOGGER.trace("Skipping lifecycle evaluation because there is no focus"); - return; - } - - ResourceObjectLifecycleDefinitionType lifecycleDef = null; - ResourceObjectTypeDefinitionType resourceAccountDefType = projCtx.getResourceObjectTypeDefinitionType(); - if (resourceAccountDefType != null) { - lifecycleDef = resourceAccountDefType.getLifecycle(); - } - ResourceBidirectionalMappingType lifecycleStateMappingType = null; - if (lifecycleDef != null) { - lifecycleStateMappingType = lifecycleDef.getLifecycleState(); - } + @ProcessorMethod + void processLifecycle(LensContext context, LensProjectionContext projCtx, + @SuppressWarnings("unused") String activityDescription, XMLGregorianCalendar now, Task task, OperationResult result) + throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, + CommunicationException, ConfigurationException, SecurityViolationException { - if (lifecycleStateMappingType == null || lifecycleStateMappingType.getOutbound() == null) { + ResourceBidirectionalMappingType lifecycleStateMapping = getLifecycleStateMapping(projCtx); + if (lifecycleStateMapping == null || lifecycleStateMapping.getOutbound() == null) { if (!projCtx.isAdd()) { - LOGGER.trace("Skipping lifecycle evaluation because this is not add operation (default expression)"); + LOGGER.trace("Skipping lifecycle evaluation because this is not an add operation (default expression)"); return; } - PrismObject focusNew = focusContext.getObjectNew(); + PrismObject focusNew = context.getFocusContext().getObjectNew(); if (focusNew == null) { LOGGER.trace("Skipping lifecycle evaluation because there is no new focus (default expression)"); return; @@ -911,18 +919,40 @@ private void processLifecycleFocus(LensContext context, PropertyDelta lifeCycleDelta = propDef.createEmptyDelta(SchemaConstants.PATH_LIFECYCLE_STATE); PrismPropertyValue pval = prismContext.itemFactory().createPropertyValue(lifecycle); pval.setOriginType(OriginType.OUTBOUND); + //noinspection unchecked lifeCycleDelta.setValuesToReplace(pval); projCtx.swallowToSecondaryDelta(lifeCycleDelta); } } else { - LOGGER.trace("Computing projection lifecycle (mapping): {}", lifecycleStateMappingType); - evaluateActivationMapping(context, projCtx, lifecycleStateMappingType, + LOGGER.trace("Computing projection lifecycle (using mapping): {}", lifecycleStateMapping); + evaluateActivationMapping(context, projCtx, lifecycleStateMapping, SchemaConstants.PATH_LIFECYCLE_STATE, SchemaConstants.PATH_LIFECYCLE_STATE, null, now, MappingTimeEval.CURRENT, ObjectType.F_LIFECYCLE_STATE.getLocalPart(), task, result); } + context.checkConsistenceIfNeeded(); + projCtx.recompute(); + context.checkConsistenceIfNeeded(); + } + + @Nullable + private ResourceBidirectionalMappingType getLifecycleStateMapping(LensProjectionContext projCtx) { + ResourceObjectLifecycleDefinitionType lifecycleDef; + ResourceObjectTypeDefinitionType resourceAccountDefType = projCtx.getResourceObjectTypeDefinitionType(); + if (resourceAccountDefType != null) { + lifecycleDef = resourceAccountDefType.getLifecycle(); + } else { + lifecycleDef = null; + } + ResourceBidirectionalMappingType lifecycleStateMapping; + if (lifecycleDef != null) { + lifecycleStateMapping = lifecycleDef.getLifecycleState(); + } else { + lifecycleStateMapping = null; + } + return lifecycleStateMapping; } private PrismObjectDefinition getUserDefinition() { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Components.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Components.java new file mode 100644 index 00000000000..d3754061362 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Components.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020 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; + +/** + * Names of projector/clockwork components invoked by medic.partialExecute method calls. + */ +@SuppressWarnings("WeakerAccess") +public class Components { + + public static final String LOAD = "load"; + public static final String FOCUS = "focus"; + public static final String INBOUND = "inbound"; + public static final String FOCUS_ACTIVATION = "focusActivation"; + public static final String OBJECT_TEMPLATE_BEFORE_ASSIGNMENTS = "objectTemplateBeforeAssignments"; + public static final String ASSIGNMENTS = "assignments"; + public static final String ASSIGNMENTS_ORG = "assignmentsOrg"; + public static final String ASSIGNMENTS_MEMBERSHIP_AND_DELEGATE = "assignmentsMembershipAndDelegate"; + public static final String ASSIGNMENTS_CONFLICTS = "assignmentsConflicts"; + public static final String FOCUS_LIFECYCLE = "focusLifecycle"; + public static final String OBJECT_TEMPLATE_AFTER_ASSIGNMENTS = "objectTemplateAfterAssignments"; + public static final String FOCUS_CREDENTIALS = "focusCredentials"; + public static final String FOCUS_POLICY_RULES = "focusPolicyRules"; + public static final String EXECUTION = "execution"; + public static final String PROJECTION = "projection"; + public static final String PROJECTION_VALUES = "projectionValues"; + public static final String PROJECTION_CREDENTIALS = "projectionCredentials"; + public static final String PROJECTION_RECONCILIATION = "projectionReconciliation"; + public static final String PROJECTION_VALUES_POST_RECON = "projectionValuesPostRecon"; + public static final String PROJECTION_LIFECYCLE = "projectionLifecycle"; +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ConsolidationProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ConsolidationProcessor.java index 5d90b7828ef..fcb7f286447 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ConsolidationProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ConsolidationProcessor.java @@ -75,14 +75,9 @@ public class ConsolidationProcessor { private PrismContainerDefinition associationDefinition; - @Autowired - private ContextLoader contextLoader; - - @Autowired - private MatchingRuleRegistry matchingRuleRegistry; - - @Autowired - PrismContext prismContext; + @Autowired private ContextLoader contextLoader; + @Autowired private MatchingRuleRegistry matchingRuleRegistry; + @Autowired private PrismContext prismContext; /** * Converts delta set triples to a secondary account deltas. @@ -104,14 +99,16 @@ void consolidateValues(LensContext context, LensProject SynchronizationPolicyDecision policyDecision = accCtx.getSynchronizationPolicyDecision(); - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); if (policyDecision == SynchronizationPolicyDecision.DELETE) { // Nothing to do } else { // This is ADD, KEEP, UNLINK or null. All are in fact the same as KEEP consolidateValuesModifyProjection(context, accCtx, task, result); + context.checkConsistenceIfNeeded(); } - if (consistencyChecks) context.checkConsistence(); + context.recompute(); + context.checkConsistenceIfNeeded(); } catch (Throwable t) { result.recordFatalError(t.getMessage(), t); throw t; 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 74b04ed4b4e..9bf264c1d89 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 @@ -14,6 +14,8 @@ import java.util.Iterator; import java.util.List; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.schema.*; @@ -65,6 +67,8 @@ import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; +import javax.xml.datatype.XMLGregorianCalendar; + /** * 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 @@ -74,7 +78,8 @@ * */ @Component -public class ContextLoader { +@ProcessorExecution() +public class ContextLoader implements ProjectorProcessor { @Autowired @Qualifier("cacheRepositoryService") @@ -93,13 +98,12 @@ public class ContextLoader { 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) + @ProcessorMethod + void load(LensContext context, String activityDescription, + @SuppressWarnings("unused") XMLGregorianCalendar now, Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException { - context.checkAbortRequested(); - context.recompute(); OperationResult result = parentResult.createMinorSubresult(OPERATION_LOAD); @@ -121,7 +125,7 @@ public void load(LensContext context, String activityD preprocessProjectionContext(context, projectionContext, task, result); } - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); determineFocusContext(context, task, result); @@ -163,7 +167,7 @@ public void load(LensContext context, String activityD removeRottenContexts(context); - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); for (LensProjectionContext projectionContext: context.getProjectionContexts()) { context.checkAbortRequested(); @@ -179,14 +183,17 @@ public void load(LensContext context, String activityD projectionResult.computeStatus(); } - if (consistencyChecks) context.checkConsistence(); - + context.checkConsistenceIfNeeded(); context.recompute(); if (consistencyChecks) { fullCheckConsistence(context); } + // Set the "fresh" mark now so following consistency check will be stricter + context.setFresh(true); + context.checkConsistenceIfNeeded(); + medic.traceContext(LOGGER, activityDescription, "after load", false, context, false); result.computeStatusComposite(); @@ -250,8 +257,10 @@ private void removeRottenContexts(LensContext context) /** * 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 { + 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); } @@ -492,7 +501,7 @@ private void loadFromSystemConfig(LensContext context, if (context.getFocusTemplate() == null) { // TODO is the nullity check needed here? - setFocusTemplate(context, result); + updateFocusTemplate(context, result); } if (context.getAccountSynchronizationSettings() == null) { @@ -555,7 +564,7 @@ public void updateArchetypePolicy(LensContext context, } // expects that object policy configuration is already set in focusContext - public void setFocusTemplate(LensContext context, OperationResult result) + public void updateFocusTemplate(LensContext context, OperationResult result) throws ObjectNotFoundException, SchemaException { // 1. When this method is called after inbound processing, we might want to change the existing template @@ -616,17 +625,17 @@ private void loadLinkRefs(LensContext context, Task tas LOGGER.trace("loadLinkRefsFromFocus done"); } - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); loadLinkRefsFromDelta(context, focusCurrent, focusContext, task, result); LOGGER.trace("loadLinkRefsFromDelta done"); - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); loadProjectionContextsSync(context, task, result); LOGGER.trace("loadProjectionContextsSync done"); - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); } /** diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/DependencyProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/DependencyProcessor.java index 0b185b30b96..b7f4e644459 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/DependencyProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/DependencyProcessor.java @@ -12,7 +12,6 @@ import java.util.List; import com.evolveum.midpoint.schema.GetOperationOptions; -import com.evolveum.midpoint.schema.PointInTimeType; import com.evolveum.midpoint.schema.SelectorOptions; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.task.api.TaskManager; @@ -22,7 +21,6 @@ import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.Resource; import org.springframework.stereotype.Component; import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; @@ -54,9 +52,6 @@ public class DependencyProcessor { @Autowired private TaskManager taskManager; - public void resetWaves(LensContext context) throws PolicyViolationException { - } - public void sortProjectionsToWaves(LensContext context) throws PolicyViolationException { // Create a snapshot of the projection collection at the beginning of computation. // The collection may be changed during computation (projections may be added). We do not want to process @@ -89,16 +84,6 @@ public void sortProjectionsToWaves(LensContext context } } - public int computeMaxWaves(LensContext context) { - if (context.getPartialProcessingOptions().getInbound() != PartialProcessingTypeType.SKIP) { - // Let's do one extra wave with no accounts in it. This time we expect to get the results of the execution to the user - // via inbound, e.g. identifiers generated by the resource, DNs and similar things. Hence the +2 instead of +1 - return context.getMaxWave() + 2; - } else { - return context.getMaxWave() + 1; - } - } - private LensProjectionContext determineProjectionWave(LensContext context, LensProjectionContext projectionContext, ResourceObjectTypeDependencyType inDependency, List depPath) throws PolicyViolationException { if (!projectionContext.isWaveIncomplete()) { @@ -207,18 +192,16 @@ private LensProjectionContext determineProjectionWaveProv } private String getResourceNameFromRef(ResourceShadowDiscriminator refDiscr) { - String name = null; try { Task task = taskManager.createTaskInstance("Load resource"); GetOperationOptions rootOpts = GetOperationOptions.createNoFetch(); Collection> options = SelectorOptions.createCollection(rootOpts); PrismObject resource = provisioningService.getObject(ResourceType.class, refDiscr.getResourceOid(), options, task, task.getResult()); - name = resource.getName().getOrig(); + return resource.getName().getOrig(); } catch (Exception e) { //ignoring exception and return null return null; } - return name; } private LensProjectionContext determineProjectionWaveDeprovision(LensContext context, @@ -305,7 +288,7 @@ private LensProjectionContext determineProjectionWaveDepr } private Collection findReverseDependecies(LensContext context, - LensProjectionContext targetProjectionContext) throws PolicyViolationException { + LensProjectionContext targetProjectionContext) { Collection deps = new ArrayList<>(); for (LensProjectionContext projectionContext: context.getProjectionContexts()) { for (ResourceObjectTypeDependencyType dependency: projectionContext.getDependencies()) { @@ -391,9 +374,7 @@ private LensProjectionContext findDependencyTargetContext // } private LensProjectionContext createAnotherContext(LensContext context, LensProjectionContext origProjectionContext, - ResourceShadowDiscriminator discr) throws PolicyViolationException { - - + ResourceShadowDiscriminator discr) { LensProjectionContext otherCtx = context.createProjectionContext(discr); otherCtx.setResource(origProjectionContext.getResource()); // Force recon for the new context. This is a primitive way how to avoid phantom changes. @@ -402,19 +383,18 @@ private LensProjectionContext createAnotherContext(LensCo } private LensProjectionContext createAnotherContext(LensContext context, LensProjectionContext origProjectionContext, - int determinedOrder) throws PolicyViolationException { + int determinedOrder) { ResourceShadowDiscriminator origDiscr = origProjectionContext.getResourceShadowDiscriminator(); ResourceShadowDiscriminator discr = new ResourceShadowDiscriminator(origDiscr.getResourceOid(), origDiscr.getKind(), origDiscr.getIntent(), origDiscr.getTag(), origDiscr.isTombstone()); discr.setOrder(determinedOrder); - LensProjectionContext otherCtx = createAnotherContext(context, origProjectionContext, discr); - return otherCtx; + return createAnotherContext(context, origProjectionContext, discr); } /** - * Check that the dependencies are still satisfied. Also check for high-ordes vs low-order operation consistency + * Check that the dependencies are still satisfied. Also check for high-orders vs low-order operation consistency * and stuff like that. */ - public boolean checkDependencies(LensContext context, + boolean checkDependencies(LensContext context, LensProjectionContext projContext, OperationResult result) throws PolicyViolationException { if (projContext.isDelete()) { // It is OK if we depend on something that is not there if we are being removed ... for now @@ -503,15 +483,15 @@ public boolean checkDependencies(LensContext context, return true; } - public void preprocessDependencies(LensContext context){ + void preprocessDependencies(LensContext context){ //in the first wave we do not have enough information to preprocess contexts - if (context.getExecutionWave() == 0){ + if (context.getExecutionWave() == 0) { return; } - for (LensProjectionContext projContext : context.getProjectionContexts()){ - if (!projContext.isCanProject()){ + for (LensProjectionContext projContext : context.getProjectionContexts()) { + if (!projContext.isCanProject()) { continue; } @@ -521,16 +501,13 @@ public void preprocessDependencies(LensContext context LOGGER.trace("LOOKING FOR {}", refRat); LensProjectionContext dependencyAccountContext = context.findProjectionContext(refRat); ResourceObjectTypeDependencyStrictnessType strictness = ResourceTypeUtil.getDependencyStrictness(dependency); - if (dependencyAccountContext != null){ - if (!dependencyAccountContext.isCanProject()){ - continue; - } + if (dependencyAccountContext != null && dependencyAccountContext.isCanProject()) { // We have the context of the object that we depend on. We need to check if it was provisioned. if (strictness == ResourceObjectTypeDependencyStrictnessType.STRICT || strictness == ResourceObjectTypeDependencyStrictnessType.RELAXED) { if (wasExecuted(dependencyAccountContext)) { // everything OK - if (ResourceTypeUtil.isForceLoadDependentShadow(dependency) && !dependencyAccountContext.isDelete()){ + if (ResourceTypeUtil.isForceLoadDependentShadow(dependency) && !dependencyAccountContext.isDelete()) { dependencyAccountContext.setDoReconciliation(true); projContext.setDoReconciliation(true); } @@ -539,14 +516,18 @@ public void preprocessDependencies(LensContext context } } } - } /** - * Finally checks for all the dependencies. Some dependencies cannot be checked during wave computations as - * we might not have all activation decisions yet. + * Original comment (since 2014): + * Finally checks for all the dependencies. Some dependencies cannot be checked during wave computations as + * we might not have all activation decisions yet. + * + * However, for almost five years this method is called at end of each projection wave, i.e. not + * only at the real end. (With the exception of previewChanges regime.) So let's keep executing + * it in this way in both normal + preview modes. */ - public void checkDependenciesFinal(LensContext context, OperationResult result) throws PolicyViolationException { + void checkDependenciesFinal(LensContext context, OperationResult result) throws PolicyViolationException { for (LensProjectionContext accountContext: context.getProjectionContexts()) { checkDependencies(context, accountContext, result); @@ -585,7 +566,7 @@ public void checkDependenciesFinal(LensContext context } } - private boolean wasProvisioned(LensProjectionContext projectionContext, int executionWave) { + private boolean wasProvisioned(LensProjectionContext projectionContext, int executionWave) { int accountWave = projectionContext.getWave(); if (accountWave >= executionWave) { // This had no chance to be provisioned yet, so we assume it will be provisioned @@ -644,7 +625,7 @@ private boolean wasExecuted(LensProjectionContext accountContext){ } List> executedDeltas = accountContext.getExecutedDeltas(); - if (executedDeltas == null || executedDeltas.isEmpty()) { + if (executedDeltas.isEmpty()) { return false; } } 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 b3da95829bd..7d6de084c61 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 @@ -170,6 +170,9 @@ void processOutbound(LensContext context, LensProjectio if (nextRecompute != null) { nextRecompute.createTrigger(context.getFocusContext()); } + + context.recompute(); + context.checkConsistenceIfNeeded(); } // TODO: unify with MappingEvaluator.evaluateOutboundMapping(...) diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ProjectionValuesProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ProjectionValuesProcessor.java index f32125be3af..d02b1adbc56 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ProjectionValuesProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ProjectionValuesProcessor.java @@ -6,17 +6,18 @@ */ package com.evolveum.midpoint.model.impl.lens.projector; -import static com.evolveum.midpoint.schema.internals.InternalsConfig.consistencyChecks; - import java.util.Collection; import java.util.Iterator; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; import com.evolveum.midpoint.model.impl.sync.SynchronizationService; import com.evolveum.midpoint.model.impl.util.ModelImplUtils; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @@ -27,11 +28,9 @@ import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; import com.evolveum.midpoint.model.impl.lens.LensUtil; import com.evolveum.midpoint.model.impl.lens.projector.focus.AssignmentProcessor; -import com.evolveum.midpoint.model.impl.sync.SynchronizationExpressionsEvaluator; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.delta.PropertyDelta; @@ -59,6 +58,10 @@ import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; +import javax.xml.datatype.XMLGregorianCalendar; + +import static java.util.Objects.requireNonNull; + /** * Processor that determines values of account attributes. It does so by taking the pre-processed information left * behind by the assignment processor. It also does some checks, such as check of identifier uniqueness. It tries to @@ -67,63 +70,38 @@ * @author Radovan Semancik */ @Component -public class ProjectionValuesProcessor { +@ProcessorExecution(focusRequired = true, focusType = FocusType.class) +public class ProjectionValuesProcessor implements ProjectorProcessor { private static final Trace LOGGER = TraceManager.getTrace(ProjectionValuesProcessor.class); - @Autowired - private OutboundProcessor outboundProcessor; - - @Autowired - private ConsolidationProcessor consolidationProcessor; - - @Autowired - private AssignmentProcessor assignmentProcessor; - - @Autowired - @Qualifier("cacheRepositoryService") - RepositoryService repositoryService; - - @Autowired - private ExpressionFactory expressionFactory; - - @Autowired - private PrismContext prismContext; - - @Autowired - private SynchronizationExpressionsEvaluator correlationConfirmationEvaluator; - - @Autowired - private SynchronizationService synchronizationService; - - @Autowired - private ContextLoader contextLoader; - - @Autowired - private ProvisioningService provisioningService; - - public void process(LensContext context, - LensProjectionContext projectionContext, String activityDescription, Task task, OperationResult result) + @Autowired private OutboundProcessor outboundProcessor; + @Autowired private ConsolidationProcessor consolidationProcessor; + @Autowired private AssignmentProcessor assignmentProcessor; + @Autowired @Qualifier("cacheRepositoryService") RepositoryService repositoryService; + @Autowired private ExpressionFactory expressionFactory; + @Autowired private PrismContext prismContext; + @Autowired private SynchronizationService synchronizationService; + @Autowired private ContextLoader contextLoader; + @Autowired private ProvisioningService provisioningService; + + @ProcessorMethod + public void process(LensContext context, LensProjectionContext projectionContext, + String activityDescription, @SuppressWarnings("unused") XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - return; - } - if (!FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - // We can do this only for focus types. - return; - } - processProjections((LensContext) context, projectionContext, - activityDescription, task, result); + processProjectionValues(context, projectionContext, activityDescription, task, result); + context.checkConsistenceIfNeeded(); + projectionContext.recompute(); + context.checkConsistenceIfNeeded(); } - private void processProjections(LensContext context, + private void processProjectionValues(LensContext context, LensProjectionContext projContext, String activityDescription, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException { - checkSchemaAndPolicies(context, projContext, activityDescription, result); + checkSchemaAndPolicies(projContext, activityDescription); SynchronizationPolicyDecision policyDecision = projContext.getSynchronizationPolicyDecision(); if (policyDecision == SynchronizationPolicyDecision.UNLINK) { @@ -135,7 +113,7 @@ private void processProjections(LensContext context, return; } - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); if (!projContext.hasFullShadow() && hasIterationExpression(projContext)) { contextLoader.loadFullShadow(context, projContext, "iteration expression", task, result); @@ -171,7 +149,7 @@ private void processProjections(LensContext context, // These are normally null. But there may be leftover from the previous iteration. // While that should not affect the algorithm (it should overwrite it) it may confuse - // people during debugging and unecessarily clutter the debug output. + // people during debugging and unnecessarily clutter the debug output. projContext.setOutboundConstruction(null); projContext.setSqueezedAttributes(null); projContext.setSqueezedAssociations(null); @@ -179,8 +157,6 @@ private void processProjections(LensContext context, LOGGER.trace("Projection values iteration {}, token '{}' for {}", iteration, iterationToken, projContext.getHumanReadableName()); -// LensUtil.traceContext(LOGGER, activityDescription, "values (start)", false, context, true); - if (!evaluateIterationCondition(context, projContext, iteration, iterationToken, true, task, result)) { conflictMessage = "pre-iteration condition was false"; @@ -188,34 +164,23 @@ private void processProjections(LensContext context, iteration, iterationToken, projContext.getHumanReadableName()); } else { - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); // Re-evaluates the values in the account constructions (including roles) assignmentProcessor.processAssignmentsAccountValues(projContext, result); context.recompute(); - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); // policyRuleProcessor.evaluateShadowPolicyRules(context, projContext, activityDescription, task, result); -// LensUtil.traceContext(LOGGER, activityDescription, "values (assignment account values)", false, context, true); - // Evaluates the values in outbound mappings outboundProcessor.processOutbound(context, projContext, task, result); - context.recompute(); - if (consistencyChecks) context.checkConsistence(); - -// LensUtil.traceContext(LOGGER, activityDescription, "values (outbound)", false, context, true); - // Merges the values together, processing exclusions and strong/weak mappings are needed consolidationProcessor.consolidateValues(context, projContext, task, result); - if (consistencyChecks) context.checkConsistence(); - context.recompute(); - if (consistencyChecks) context.checkConsistence(); - // Aux object classes may have changed during consolidation. Make sure we have up-to-date definitions. context.refreshAuxiliaryObjectClassDefinitions(); @@ -229,14 +194,10 @@ private void processProjections(LensContext context, iterationToken = null; cleanupContext(projContext, null); LOGGER.trace("Resetting iteration counter and token because we have rename"); - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); continue; } - // Too noisy for now -// LensUtil.traceContext(LOGGER, activityDescription, "values (consolidation)", false, context, true); - - if (policyDecision == SynchronizationPolicyDecision.DELETE) { // No need to play the iterative game if the account is deleted break; @@ -418,11 +379,11 @@ private void processProjections(LensContext context, LensUtil.checkMaxIterations(iteration, maxIterations, conflictMessage, projContext.getHumanReadableName()); cleanupContext(projContext, null); - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); } addIterationTokenDeltas(projContext); - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); } private boolean willResetIterationCounter(LensProjectionContext projectionContext) throws SchemaException { @@ -446,8 +407,7 @@ private boolean willResetIterationCounter(LensProjectionContext projectionContex return false; } - - + @SuppressWarnings("RedundantIfStatement") private boolean hasIterationExpression(LensProjectionContext accountContext) { ResourceObjectTypeDefinitionType accDef = accountContext.getResourceObjectTypeDefinitionType(); if (accDef == null) { @@ -517,8 +477,8 @@ private boolean evaluateIterationCondition(LensContext * Check that the primary deltas do not violate schema and policies * TODO: implement schema check */ - public void checkSchemaAndPolicies(LensContext context, - LensProjectionContext accountContext, String activityDescription, OperationResult result) throws SchemaException, PolicyViolationException { + private void checkSchemaAndPolicies(LensProjectionContext accountContext, String activityDescription) + throws SchemaException, PolicyViolationException { ObjectDelta primaryDelta = accountContext.getPrimaryDelta(); if (primaryDelta == null || primaryDelta.isDelete()) { return; @@ -535,7 +495,7 @@ public void checkSchemaAndPolicies(LensContext context ResourceAttributeContainer attributesContainer = ShadowUtil.getAttributesContainer(accountToAdd); if (attributesContainer != null) { for (ResourceAttribute attribute: attributesContainer.getAttributes()) { - RefinedAttributeDefinition rAttrDef = rAccountDef.findAttributeDefinition(attribute.getElementName()); + RefinedAttributeDefinition rAttrDef = requireNonNull(rAccountDef.findAttributeDefinition(attribute.getElementName())); if (!rAttrDef.isTolerant()) { throw new PolicyViolationException("Attempt to add object with non-tolerant attribute "+attribute.getElementName()+" in "+ "account "+accountContext.getResourceShadowDiscriminator()+" during "+activityDescription); @@ -546,7 +506,7 @@ public void checkSchemaAndPolicies(LensContext context for(ItemDelta modification: primaryDelta.getModifications()) { if (modification.getParentPath().equivalent(SchemaConstants.PATH_ATTRIBUTES)) { PropertyDelta attrDelta = (PropertyDelta) modification; - RefinedAttributeDefinition rAttrDef = rAccountDef.findAttributeDefinition(attrDelta.getElementName()); + RefinedAttributeDefinition rAttrDef = requireNonNull(rAccountDef.findAttributeDefinition(attrDelta.getElementName())); if (!rAttrDef.isTolerant()) { throw new PolicyViolationException("Attempt to modify non-tolerant attribute "+attrDelta.getElementName()+" in "+ "account "+accountContext.getResourceShadowDiscriminator()+" during "+activityDescription); @@ -563,42 +523,40 @@ public void checkSchemaAndPolicies(LensContext context */ private void cleanupContext(LensProjectionContext accountContext, PrismObject fullConflictingShadow) throws SchemaException { // We must NOT clean up activation computation here. This has happened before, it will not happen again - // and it does not depend on iteration. But, in fact we want to cleaup up activation changes if they + // and it does not depend on iteration. But, in fact we want to cleanup up activation changes if they // are already applied to the new shadow. ObjectDelta secondaryDelta = accountContext.getSecondaryDelta(); if (secondaryDelta != null) { boolean administrativeStatusDeltaRemoved = false; Collection modifications = secondaryDelta.getModifications(); - if (modifications != null) { - Iterator iterator = modifications.iterator(); - while (iterator.hasNext()) { - ItemDelta modification = iterator.next(); - if (SchemaConstants.PATH_ACTIVATION.equivalent(modification.getParentPath())) { - if (fullConflictingShadow != null) { - if (QNameUtil.match(ActivationType.F_ADMINISTRATIVE_STATUS, modification.getElementName())) { - if (modification.isRedundant(fullConflictingShadow, false)) { - LOGGER.trace("Removing redundant secondary activation delta: {}", modification); - iterator.remove(); - } - administrativeStatusDeltaRemoved = true; + Iterator iterator = modifications.iterator(); + while (iterator.hasNext()) { + ItemDelta modification = iterator.next(); + if (SchemaConstants.PATH_ACTIVATION.equivalent(modification.getParentPath())) { + if (fullConflictingShadow != null) { + if (QNameUtil.match(ActivationType.F_ADMINISTRATIVE_STATUS, modification.getElementName())) { + if (modification.isRedundant(fullConflictingShadow, false)) { + LOGGER.trace("Removing redundant secondary activation delta: {}", modification); + iterator.remove(); } + administrativeStatusDeltaRemoved = true; } - } else { - iterator.remove(); } + } else { + iterator.remove(); } - if (administrativeStatusDeltaRemoved) { - iterator = modifications.iterator(); - while (iterator.hasNext()) { - ItemDelta modification = iterator.next(); - if (SchemaConstants.PATH_ACTIVATION.equivalent(modification.getParentPath())) { - if (QNameUtil.match(ActivationType.F_ENABLE_TIMESTAMP, modification.getElementName()) || - QNameUtil.match(ActivationType.F_DISABLE_TIMESTAMP, modification.getElementName()) || - QNameUtil.match(ActivationType.F_DISABLE_REASON, modification.getElementName()) || - QNameUtil.match(ActivationType.F_ARCHIVE_TIMESTAMP, modification.getElementName())) { - LOGGER.trace("Removing secondary activation delta because redundant delta was removed before: {}", modification); - iterator.remove(); - } + } + if (administrativeStatusDeltaRemoved) { + iterator = modifications.iterator(); + while (iterator.hasNext()) { + ItemDelta modification = iterator.next(); + if (SchemaConstants.PATH_ACTIVATION.equivalent(modification.getParentPath())) { + if (QNameUtil.match(ActivationType.F_ENABLE_TIMESTAMP, modification.getElementName()) || + QNameUtil.match(ActivationType.F_DISABLE_TIMESTAMP, modification.getElementName()) || + QNameUtil.match(ActivationType.F_DISABLE_REASON, modification.getElementName()) || + QNameUtil.match(ActivationType.F_ARCHIVE_TIMESTAMP, modification.getElementName())) { + LOGGER.trace("Removing secondary activation delta because redundant delta was removed before: {}", modification); + iterator.remove(); } } } @@ -611,7 +569,6 @@ private void cleanupContext(LensProjectionContext accountContext, PrismObject void processPostRecon(LensContext context, - LensProjectionContext projectionContext, String activityDescription, Task task, OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException, - CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - return; - } - if (!FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - // We can do this only for focus types. - return; - } - processProjectionsPostRecon((LensContext) context, projectionContext, - activityDescription, task, result); - } - - private void processProjectionsPostRecon(LensContext context, - LensProjectionContext projContext, String activityDescription, Task task, OperationResult result) - throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException, - CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException { - + @ProcessorMethod + void processPostRecon(LensContext context, LensProjectionContext projContext, + @SuppressWarnings("unused") String activityDescription, @SuppressWarnings("unused") XMLGregorianCalendar now, + Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, PolicyViolationException { SynchronizationPolicyDecision policyDecision = projContext.getSynchronizationPolicyDecision(); if (policyDecision == SynchronizationPolicyDecision.UNLINK) { // We will not update accounts that are being unlinked. @@ -675,7 +615,8 @@ private void processProjectionsPostRecon(LensContext co } consolidationProcessor.consolidateValuesPostRecon(context, projContext, task, result); - + context.checkConsistenceIfNeeded(); + projContext.recompute(); + context.checkConsistenceIfNeeded(); } - } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java index b8f7503ec21..af656b169ad 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Projector.java @@ -9,13 +9,11 @@ import static com.evolveum.midpoint.model.api.ProgressInformation.ActivityType.PROJECTOR; import static com.evolveum.midpoint.model.api.ProgressInformation.StateType.ENTERING; import static com.evolveum.midpoint.model.impl.lens.LensUtil.getExportType; -import static com.evolveum.midpoint.schema.internals.InternalsConfig.consistencyChecks; - -import java.util.List; import javax.xml.datatype.XMLGregorianCalendar; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ProjectorRunTraceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -25,14 +23,11 @@ import com.evolveum.midpoint.model.impl.lens.ClockworkMedic; import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; -import com.evolveum.midpoint.model.impl.lens.LensUtil; import com.evolveum.midpoint.model.impl.lens.projector.credentials.ProjectionCredentialsProcessor; import com.evolveum.midpoint.model.impl.lens.projector.focus.AssignmentHolderProcessor; -import com.evolveum.midpoint.model.impl.lens.projector.focus.AssignmentProcessor; import com.evolveum.midpoint.model.impl.util.ModelImplUtils; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.repo.api.PreconditionViolationException; -import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; import com.evolveum.midpoint.schema.internals.InternalCounters; import com.evolveum.midpoint.schema.internals.InternalMonitor; import com.evolveum.midpoint.schema.result.OperationResult; @@ -48,9 +43,6 @@ import com.evolveum.midpoint.util.exception.SecurityViolationException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.PartialProcessingOptionsType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.PartialProcessingTypeType; /** * Projector recomputes the context. It takes the context with a few basic data as input. It uses all the policies @@ -73,8 +65,7 @@ public class Projector { private static final String OPERATION_PROJECT_PROJECTION = Projector.class.getName() + ".projectProjection"; @Autowired private ContextLoader contextLoader; - @Autowired private AssignmentHolderProcessor focusProcessor; - @Autowired private AssignmentProcessor assignmentProcessor; + @Autowired private AssignmentHolderProcessor assignmentHolderProcessor; @Autowired private ProjectionValuesProcessor projectionValuesProcessor; @Autowired private ReconciliationProcessor reconciliationProcessor; @Autowired private ProjectionCredentialsProcessor projectionCredentialsProcessor; @@ -92,26 +83,21 @@ public void project(LensContext context, String activi OperationResult parentResult) throws SchemaException, PolicyViolationException, ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException, PreconditionViolationException { - projectInternal(context, activityDescription, true, false, task, parentResult); + context.normalize(); + context.resetProjectionWave(); + projectInternal(context, activityDescription, true, task, parentResult); } /** * Resumes projection at current projection wave. */ - public void resume(LensContext context, String activityDescription, - Task task, OperationResult parentResult) + public void resume(LensContext context, String activityDescription, Task task, + OperationResult parentResult) throws SchemaException, PolicyViolationException, ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException, PreconditionViolationException { - - if (context.getProjectionWave() != context.getExecutionWave()) { - throw new IllegalStateException("Projector.resume called in illegal wave state: execution wave = " + context.getExecutionWave() + - ", projection wave = " + context.getProjectionWave()); - } - if (!context.isFresh()) { - throw new IllegalStateException("Projector.resume called on non-fresh context"); - } - - projectInternal(context, activityDescription, false, false, task, parentResult); + assert context.getProjectionWave() == context.getExecutionWave(); + assert context.isFresh(); + projectInternal(context, activityDescription, false, task, parentResult); } /** @@ -122,11 +108,17 @@ public void projectAllWaves(LensContext context, Strin Task task, OperationResult parentResult) throws SchemaException, PolicyViolationException, ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException, PreconditionViolationException { - projectInternal(context, activityDescription, true, true, task, parentResult); + assert context.getProjectionWave() == 0; + assert context.getExecutionWave() == 0; + context.normalize(); + while (context.getProjectionWave() < context.computeMaxWaves()) { + boolean fromStart = context.getProjectionWave() == 0; + projectInternal(context, activityDescription, fromStart, task, parentResult); + } } private void projectInternal(LensContext context, String activityDescription, - boolean fromStart, boolean allWaves, Task task, OperationResult parentResult) + boolean fromStart, Task task, OperationResult parentResult) throws SchemaException, PolicyViolationException, ExpressionEvaluationException, ObjectNotFoundException, ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException, PreconditionViolationException { @@ -146,20 +138,12 @@ private void projectInternal(LensContext context, Stri String traceTitle = fromStart ? "projector start" : "projector resume"; medic.traceContext(LOGGER, activityDescription, traceTitle, false, context, false); -// setupCounters(now, context.getPrismContext(), task, parentResult); - - if (consistencyChecks) context.checkConsistence(); - - if (fromStart) { - context.normalize(); - context.resetProjectionWave(); - } + context.checkConsistenceIfNeeded(); OperationResult result = parentResult.subresult(Projector.class.getName() + ".project") .addQualifier(context.getOperationQualifier()) .addParam("fromStart", fromStart) .addContext("projectionWave", context.getProjectionWave()) - .addContext("executionWave", context.getExecutionWave()) .build(); ProjectorRunTraceType trace; if (result.isTraced()) { @@ -180,119 +164,53 @@ private void projectInternal(LensContext context, Stri context.reportProgress(new ProgressInformation(PROJECTOR, ENTERING)); if (fromStart) { - medic.partialExecute("load", - (result1) -> { - contextLoader.load(context, activityDescription, task, result1); - // Set the "fresh" mark now so following consistency check will be stricter - context.setFresh(true); - if (consistencyChecks) context.checkConsistence(); - }, + medic.partialExecute(Components.LOAD, contextLoader, contextLoader::load, partialProcessingOptions::getLoad, - Projector.class, context, result); + Projector.class, context, activityDescription, now, task, result); } - // For now let's pretend to do just one wave. The maxWaves number will be corrected in the - // first wave when dependencies are sorted out for the first time. - int maxWaves = context.getExecutionWave() + 1; - - // Start the waves .... - LOGGER.trace("WAVE: Starting the waves."); - - boolean firstWave = true; - - while ((allWaves && context.getProjectionWave() < maxWaves) || - (!allWaves && context.getProjectionWave() <= context.getExecutionWave())) { - - boolean inFirstWave = firstWave; - firstWave = false; // in order to not forget to reset it ;) - - context.checkAbortRequested(); - - LOGGER.trace("WAVE {} (maxWaves={}, executionWave={})", - context.getProjectionWave(), maxWaves, context.getExecutionWave()); - - //just make sure everything is loaded and set as needed - dependencyProcessor.preprocessDependencies(context); - - // Process the focus-related aspects of the context. That means inbound, focus activation, - // object template and assignments. - - medic.partialExecute("focus", - (result1) -> { - focusProcessor.processFocus(context, activityDescription, now, task, result1); - context.recomputeFocus(); - if (consistencyChecks) context.checkConsistence(); - }, - partialProcessingOptions::getFocus, - Projector.class, context, result); - - medic.traceContext(LOGGER, activityDescription, "focus processing", false, context, false); - LensUtil.checkContextSanity(context, "focus processing", result); - - if (partialProcessingOptions.getProjection() != PartialProcessingTypeType.SKIP) { - // Process activation of all resources, regardless of the waves. This is needed to properly - // sort projections to waves as deprovisioning will reverse the dependencies. And we know whether - // a projection is provisioned or deprovisioned only after the activation is processed. - if (fromStart && inFirstWave) { - OperationResult activationResult = result.subresult(Projector.class.getName() + ".activation") - .setMinor() - .build(); - try { - LOGGER.trace("Processing activation for all contexts"); - for (LensProjectionContext projectionContext : context.getProjectionContexts()) { - if (projectionContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN - || projectionContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.IGNORE) { - continue; - } - activationProcessor.processActivation(context, projectionContext, now, task, activationResult); - projectionContext.recompute(); - } - assignmentProcessor.removeIgnoredContexts( - context); // TODO move implementation of this method elsewhere; but it has to be invoked here, as activationProcessor sets the IGNORE flag - } catch (Throwable t) { - activationResult.recordFatalError(t); - throw t; - } finally { - activationResult.computeStatusIfUnknown(); - } - } - medic.traceContext(LOGGER, activityDescription, "projection activation of all resources", true, context, - true); - if (consistencyChecks) { - context.checkConsistence(); - } - dependencyProcessor.sortProjectionsToWaves(context); - maxWaves = dependencyProcessor.computeMaxWaves(context); - LOGGER.trace("Continuing wave {}, maxWaves={}", context.getProjectionWave(), maxWaves); + LOGGER.trace("WAVE {} (executionWave={})", context.getProjectionWave(), context.getExecutionWave()); - for (LensProjectionContext projectionContext : context.getProjectionContexts()) { + //just make sure everything is loaded and set as needed + dependencyProcessor.preprocessDependencies(context); - medic.partialExecute("projection", - (result1) -> projectProjection(context, projectionContext, - partialProcessingOptions, now, activityDescription, task, result1), - partialProcessingOptions::getProjection, - Projector.class, context, projectionContext, result); - // TODO: make this condition more complex in the future. We may want the ability - // to select only some projections to process + // Process the focus-related aspects of the context. That means inbound, focus activation, + // object template and assignments. - } + medic.partialExecute(Components.FOCUS, assignmentHolderProcessor, + assignmentHolderProcessor::processFocus, + partialProcessingOptions::getFocus, + Projector.class, context, activityDescription, now, task, result); - // if there exists some conflicting projection contexts, add them to the context so they will be recomputed in the next wave.. - addConflictingContexts(context); + if (partialProcessingOptions.getProjection() != PartialProcessingTypeType.SKIP) { + // Process activation of all resources, regardless of the waves. This is needed to properly + // sort projections to waves as deprovisioning will reverse the dependencies. And we know whether + // a projection is provisioned or deprovisioned only after the activation is processed. + if (fromStart) { + activationProcessor.processActivationForAllResources(context, activityDescription, now, task, result); } - if (consistencyChecks) context.checkConsistence(); + dependencyProcessor.sortProjectionsToWaves(context); + + // In the future we may want the ability to select only some projections to process. + for (LensProjectionContext projectionContext : context.getProjectionContexts()) { + medic.partialExecute(Components.PROJECTION, + (result1) -> projectProjection(context, projectionContext, + partialProcessingOptions, now, activityDescription, task, result1), + partialProcessingOptions::getProjection, + Projector.class, context, projectionContext, result); + } - context.incrementProjectionWave(); + // If there exists some conflicting projection contexts, add them to the context so they will be recomputed + // in the next wave. + addConflictingContexts(context); } - LOGGER.trace("WAVE: Stopping the waves. There was {} waves", context.getProjectionWave()); + context.checkConsistenceIfNeeded(); + context.incrementProjectionWave(); - // We can do this only when computation of all the waves is finished. Before that we do not know - // activation of every account and therefore cannot decide what is OK and what is not dependencyProcessor.checkDependenciesFinal(context, result); - - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); computeResultStatus(now, result); @@ -316,43 +234,16 @@ private void projectInternal(LensContext context, Stri } context.reportProgress(new ProgressInformation(PROJECTOR, result)); } - } -// -// private synchronized void setupCounters(XMLGregorianCalendar now, PrismContext prismContext, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { -// -// if (task.getOid() == null) { -// return; -// } -// -// TaskType taskType = task.getTaskType(); -// LOGGER.trace("Collecting counters for {}", task); -// -// AssignmentTripleEvaluator ate = new AssignmentTripleEvaluator<>(); -// ate.setNow(now); -// ate.setPrismContext(prismContext); -// ate.setResult(result); -// ate.setSource(taskType); -// ate.setTask(task); -// DeltaSetTriple> evaluatedAssignments = ate.preProcessAssignments(task.getUpdatedOrClonedTaskObject()); -// -// Set evaluatedPolicyRules = new HashSet<>(); -// -// for (EvaluatedAssignmentImpl evaluatedAssignment : evaluatedAssignments.union()) { -// evaluatedPolicyRules.addAll(evaluatedAssignment.getOtherTargetsPolicyRules()); -// } -// evaluatedPolicyRules.forEach(policyRule -> counterManager.registerCounter(task, policyRule.getPolicyRule())); -// -// } -// + private void projectProjection(LensContext context, LensProjectionContext projectionContext, PartialProcessingOptionsType partialProcessingOptions, XMLGregorianCalendar now, String activityDescription, Task task, OperationResult parentResult) throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PreconditionViolationException { - String projectionDesc = getProjectionDesc(projectionContext); - parentResult.addParam(OperationResult.PARAM_PROJECTION, projectionDesc); // a bit of hack -> to have projection info also on the root "component" operation result + String projectionDesc = projectionContext.getDescription(); + parentResult.addParam(OperationResult.PARAM_PROJECTION, projectionDesc); // a bit of hack -> to have projection info also on the root "component" operation result parentResult.addParam("resourceName", projectionContext.getResourceName()); if (projectionContext.getWave() != context.getProjectionWave()) { @@ -389,7 +280,7 @@ private void projectProjection(LensContext context, Le // Some projections may not be loaded at this point, e.g. high-order dependency projections contextLoader.makeSureProjectionIsLoaded(context, projectionContext, task, result); - if (consistencyChecks) context.checkConsistence(); + context.checkConsistenceIfNeeded(); if (!dependencyProcessor.checkDependencies(context, projectionContext, result)) { result.recordStatus(OperationResultStatus.NOT_APPLICABLE, "Skipping projection because it has unsatisfied dependencies"); @@ -398,68 +289,36 @@ private void projectProjection(LensContext context, Le // TODO: decide if we need to continue - medic.partialExecute("projectionValues", - (result1) -> { - // This is a "composite" processor. it contains several more processor invocations inside - projectionValuesProcessor.process(context, projectionContext, activityDescription, task, result1); - if (consistencyChecks) context.checkConsistence(); - - projectionContext.recompute(); - if (consistencyChecks) context.checkConsistence(); - }, + // This is a "composite" processor. it contains several more processor invocations inside + medic.partialExecute(Components.PROJECTION_VALUES, projectionValuesProcessor, + projectionValuesProcessor::process, partialProcessingOptions::getProjectionValues, - Projector.class, context, projectionContext, result); + Projector.class, context, projectionContext, activityDescription, now, task, result); if (projectionContext.isTombstone()) { result.recordStatus(OperationResultStatus.NOT_APPLICABLE, "Skipping projection because it is a tombstone"); return; } - medic.partialExecute("projectionCredentials", - (result1) -> { - projectionCredentialsProcessor.processProjectionCredentials(context, projectionContext, now, task, result1); - if (consistencyChecks) context.checkConsistence(); - - projectionContext.recompute(); - medic.traceContext(LOGGER, activityDescription, "projection values and credentials of "+projectionDesc, false, context, true); - if (consistencyChecks) context.checkConsistence(); - }, + medic.partialExecute(Components.PROJECTION_CREDENTIALS, projectionCredentialsProcessor, + projectionCredentialsProcessor::processProjectionCredentials, partialProcessingOptions::getProjectionCredentials, - Projector.class, context, projectionContext, result); - + Projector.class, context, projectionContext, activityDescription, now, task, result); - medic.partialExecute("projectionReconciliation", - (result1) -> { - reconciliationProcessor.processReconciliation(context, projectionContext, task, result1); - projectionContext.recompute(); - medic.traceContext(LOGGER, activityDescription, "projection reconciliation of "+projectionDesc, false, context, false); - if (consistencyChecks) context.checkConsistence(); - }, + medic.partialExecute(Components.PROJECTION_RECONCILIATION, reconciliationProcessor, + reconciliationProcessor::processReconciliation, partialProcessingOptions::getProjectionReconciliation, - Projector.class, context, projectionContext, result); + Projector.class, context, projectionContext, activityDescription, now, task, result); - medic.partialExecute("projectionValuesPostRecon", - (result1) -> { - projectionValuesProcessor.processPostRecon(context, projectionContext, activityDescription, task, result1); - if (consistencyChecks) context.checkConsistence(); - - projectionContext.recompute(); - if (consistencyChecks) context.checkConsistence(); - }, + medic.partialExecute(Components.PROJECTION_VALUES_POST_RECON, projectionValuesProcessor, + projectionValuesProcessor::processPostRecon, partialProcessingOptions::getProjectionValues, - Projector.class, context, projectionContext, result); - - medic.partialExecute("projectionLifecycle", - (result1) -> { - activationProcessor.processLifecycle(context, projectionContext, now, task, result1); - if (consistencyChecks) context.checkConsistence(); + Projector.class, context, projectionContext, activityDescription, now, task, result); - projectionContext.recompute(); -// LensUtil.traceContext(LOGGER, activityDescription, "projection lifecycle of "+projectionDesc, false, context, false); - if (consistencyChecks) context.checkConsistence(); - }, + medic.partialExecute(Components.PROJECTION_LIFECYCLE, activationProcessor, + activationProcessor::processLifecycle, partialProcessingOptions::getProjectionLifecycle, - Projector.class, context, projectionContext, result); + Projector.class, context, projectionContext, activityDescription, now, task, result); result.recordSuccess(); @@ -469,33 +328,14 @@ private void projectProjection(LensContext context, Le projectionContext.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.BROKEN); ModelImplUtils.handleConnectorErrorCriticality(projectionContext.getResource(), e, result); } - - - } - - private String getProjectionDesc(LensProjectionContext projectionContext) { - if (projectionContext.getResource() != null) { - return projectionContext.getResource() + "("+projectionContext.getResourceShadowDiscriminator().getIntent()+")"; - } else { - ResourceShadowDiscriminator discr = projectionContext.getResourceShadowDiscriminator(); - if (discr != null) { - return projectionContext.getResourceShadowDiscriminator().toString(); - } else { - return "(UNKNOWN)"; - } - } } private void addConflictingContexts(LensContext context) { - List conflictingContexts = context.getConflictingProjectionContexts(); - if (conflictingContexts != null && !conflictingContexts.isEmpty()){ - for (LensProjectionContext conflictingContext : conflictingContexts){ - LOGGER.trace("Adding conflicting projection context {}", conflictingContext.getHumanReadableName()); - context.addProjectionContext(conflictingContext); - } - context.clearConflictingProjectionContexts(); + for (LensProjectionContext conflictingContext : context.getConflictingProjectionContexts()) { + LOGGER.trace("Adding conflicting projection context {}", conflictingContext.getHumanReadableName()); + context.addProjectionContext(conflictingContext); } - + context.clearConflictingProjectionContexts(); } private void recordFatalError(Exception e, XMLGregorianCalendar projectorStartTimestampCal, OperationResult result) { @@ -545,5 +385,4 @@ private void computeResultStatus(XMLGregorianCalendar projectorStartTimestampCal LOGGER.trace("Projector finished ({}). Etime: {} ms", result.getStatus(), (projectorEndTimestamp - projectorStartTimestamp)); } } - } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ProjectorProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ProjectorProcessor.java new file mode 100644 index 00000000000..e5f6d97e0ce --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ProjectorProcessor.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2020 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 com.evolveum.midpoint.util.annotation.Experimental; + +/** + * Marker interface for processors in Projector. + * + * Note that functionality of the processor (i.e. some "process" method) is not exposed via this interface + * because many of the processors have multiple entry points. Moreover, to make code navigation in IDE easy + * (between processors and their caller) it is better to have direct caller-callee relations. + */ +@Experimental +public interface ProjectorProcessor { + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ReconciliationProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ReconciliationProcessor.java index fa3179a7a1a..4dbf212fc31 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ReconciliationProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ReconciliationProcessor.java @@ -14,9 +14,13 @@ import java.util.Map; import java.util.Set; +import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; import com.evolveum.midpoint.common.refinery.RefinedAssociationDefinition; +import com.evolveum.midpoint.model.impl.lens.*; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.prism.match.MatchingRule; @@ -37,11 +41,6 @@ import com.evolveum.midpoint.common.refinery.RefinedResourceSchema; import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; import com.evolveum.midpoint.model.common.mapping.PrismValueDeltaSetTripleProducer; -import com.evolveum.midpoint.model.impl.lens.ItemValueWithOrigin; -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; -import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; -import com.evolveum.midpoint.model.impl.lens.LensUtil; import com.evolveum.midpoint.prism.match.MatchingRuleRegistry; import com.evolveum.midpoint.provisioning.api.ProvisioningService; import com.evolveum.midpoint.schema.GetOperationOptions; @@ -82,38 +81,32 @@ * @author Radovan Semancik */ @Component -public class ReconciliationProcessor { +@ProcessorExecution(focusRequired = true, focusType = FocusType.class) +public class ReconciliationProcessor implements ProjectorProcessor { - @Autowired - private ProvisioningService provisioningService; + @Autowired private ProvisioningService provisioningService; + @Autowired PrismContext prismContext; + @Autowired private MatchingRuleRegistry matchingRuleRegistry; + @Autowired private ClockworkMedic medic; - @Autowired - PrismContext prismContext; - - @Autowired - private MatchingRuleRegistry matchingRuleRegistry; - - private static final String PROCESS_RECONCILIATION = ReconciliationProcessor.class.getName() + ".processReconciliation"; private static final Trace LOGGER = TraceManager.getTrace(ReconciliationProcessor.class); - void processReconciliation(LensContext context, - LensProjectionContext projectionContext, Task task, OperationResult result) throws SchemaException, + @ProcessorMethod + void processReconciliation(LensContext context, LensProjectionContext projectionContext, + String activityDescription, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - return; - } - if (!FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - // We can do this only for focal types. - return; - } - processReconciliationFocus(context, projectionContext, task, result); + + processReconciliation(projectionContext, task, result); + + projectionContext.recompute(); + context.checkConsistenceIfNeeded(); + + medic.traceContext(LOGGER, activityDescription, "projection reconciliation of "+projectionContext.getDescription(), false, context, false); } - private void processReconciliationFocus(LensContext context, - LensProjectionContext projCtx, Task task, OperationResult result) throws SchemaException, - ObjectNotFoundException, CommunicationException, ConfigurationException, + private void processReconciliation(LensProjectionContext projCtx, Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { // Reconcile even if it was not explicitly requested and if we have full shadow @@ -126,8 +119,7 @@ private void processReconciliationFocus(LensContext co } SynchronizationPolicyDecision policyDecision = projCtx.getSynchronizationPolicyDecision(); - if (policyDecision != null - && (policyDecision == SynchronizationPolicyDecision.DELETE || policyDecision == SynchronizationPolicyDecision.UNLINK)) { + if (((policyDecision == SynchronizationPolicyDecision.DELETE) || (policyDecision == SynchronizationPolicyDecision.UNLINK))) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Skipping reconciliation of {}: decision={}", projCtx.getHumanReadableName(), policyDecision); } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/CredentialsProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/CredentialsProcessor.java index 3175757bd00..2bf063fe6b7 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/CredentialsProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/credentials/CredentialsProcessor.java @@ -11,6 +11,9 @@ import javax.xml.datatype.XMLGregorianCalendar; import com.evolveum.midpoint.common.LocalizationService; +import com.evolveum.midpoint.model.impl.lens.projector.ProjectorProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.ContainerDelta; import com.evolveum.midpoint.prism.path.ItemPath; @@ -62,7 +65,8 @@ * @author Radovan Semancik */ @Component -public class CredentialsProcessor { +@ProcessorExecution(focusRequired = true, focusType = FocusType.class) +public class CredentialsProcessor implements ProjectorProcessor { private static final Trace LOGGER = TraceManager.getTrace(CredentialsProcessor.class); @@ -74,14 +78,11 @@ public class CredentialsProcessor { @Autowired private LocalizationService localizationService; @Autowired private ContextLoader contextLoader; - /** - * @pre context.focusContext is not null and of FocusType.class - */ + @ProcessorMethod public void processFocusCredentials(LensContext context, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - LensFocusContext focusContext = context.getFocusContext(); contextLoader.reloadSecurityPolicyIfNeeded(context, focusContext, task, result); 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 0477a713067..4eee63fbe52 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 @@ -13,6 +13,10 @@ import javax.xml.datatype.XMLGregorianCalendar; +import com.evolveum.midpoint.model.impl.lens.*; +import com.evolveum.midpoint.model.impl.lens.projector.ProjectorProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.prism.path.ItemPath; @@ -25,16 +29,10 @@ import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision; -import com.evolveum.midpoint.model.common.mapping.MappingFactory; import com.evolveum.midpoint.model.common.stringpolicy.ObjectValuePolicyEvaluator; import com.evolveum.midpoint.model.common.stringpolicy.ShadowValuePolicyOriginResolver; import com.evolveum.midpoint.model.common.stringpolicy.ValuePolicyProcessor; import com.evolveum.midpoint.model.impl.ModelObjectResolver; -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; -import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; -import com.evolveum.midpoint.model.impl.lens.OperationalDataManager; -import com.evolveum.midpoint.model.impl.lens.projector.ContextLoader; import com.evolveum.midpoint.model.impl.lens.projector.mappings.MappingEvaluator; import com.evolveum.midpoint.model.impl.lens.projector.mappings.MappingInitializer; import com.evolveum.midpoint.model.impl.lens.projector.mappings.MappingOutputProcessor; @@ -71,37 +69,36 @@ * @author Radovan Semancik */ @Component -public class ProjectionCredentialsProcessor { +@ProcessorExecution(focusRequired = true, focusType = FocusType.class, skipWhenProjectionDeleted = true) +public class ProjectionCredentialsProcessor implements ProjectorProcessor { private static final Trace LOGGER = TraceManager.getTrace(ProjectionCredentialsProcessor.class); @Autowired private PrismContext prismContext; - @Autowired private ContextLoader contextLoader; - @Autowired private MappingFactory mappingFactory; @Autowired private MappingEvaluator mappingEvaluator; @Autowired private ValuePolicyProcessor valuePolicyProcessor; @Autowired private Protector protector; @Autowired private OperationalDataManager operationalDataManager; @Autowired private ModelObjectResolver modelObjectResolver; + @Autowired private ClockworkMedic medic; - public void processProjectionCredentials(LensContext context, - LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, - OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, - SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { + @ProcessorMethod + public void processProjectionCredentials(LensContext context, + LensProjectionContext projectionContext, String activityDescription, XMLGregorianCalendar now, Task task, + OperationResult result) + throws ExpressionEvaluationException, ObjectNotFoundException, + SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - if (projectionContext.isDelete()) { - return; - } - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext != null && FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { + processProjectionCredentials(context, projectionContext, now, task, result); + context.checkConsistenceIfNeeded(); - processProjectionCredentialsFocus((LensContext) context, projectionContext, now, task, result); + projectionContext.recompute(); + context.checkConsistenceIfNeeded(); - } + medic.traceContext(LOGGER, activityDescription, "projection values and credentials of "+projectionContext.getDescription(), false, context, true); } - private void processProjectionCredentialsFocus(LensContext context, + private void processProjectionCredentials(LensContext context, LensProjectionContext projectionContext, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentHolderProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentHolderProcessor.java index b9aa4952dfd..6d7981d4f20 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentHolderProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentHolderProcessor.java @@ -6,65 +6,60 @@ */ package com.evolveum.midpoint.model.impl.lens.projector.focus; -import static com.evolveum.midpoint.schema.internals.InternalsConfig.consistencyChecks; -import static org.apache.commons.lang3.BooleanUtils.isTrue; - -import java.util.Map; - import javax.xml.datatype.XMLGregorianCalendar; -import com.evolveum.midpoint.model.api.context.ModelState; +import com.evolveum.midpoint.model.impl.lens.*; +import com.evolveum.midpoint.model.impl.lens.projector.Components; +import com.evolveum.midpoint.model.impl.lens.projector.ProjectorProcessor; import com.evolveum.midpoint.model.impl.lens.projector.Projector; -import com.evolveum.midpoint.model.impl.lens.projector.policy.PolicyRuleEnforcer; +import com.evolveum.midpoint.model.impl.lens.projector.credentials.CredentialsProcessor; import com.evolveum.midpoint.model.impl.lens.projector.policy.PolicyRuleProcessor; -import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.delta.*; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.prism.util.DefinitionUtil; +import com.evolveum.midpoint.repo.api.PreconditionViolationException; import com.evolveum.midpoint.schema.cache.CacheConfigurationManager; -import com.evolveum.midpoint.util.exception.NoFocusNameSchemaException; +import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.model.impl.lens.ClockworkMedic; -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; -import com.evolveum.midpoint.model.impl.lens.LensUtil; -import com.evolveum.midpoint.model.impl.lens.projector.ContextLoader; -import com.evolveum.midpoint.prism.path.UniformItemPath; -import com.evolveum.midpoint.repo.api.PreconditionViolationException; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.schema.result.OperationResult; 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.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; -import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; /** - * Processor to handle everything about focus: values, assignments, etc. + * Handles everything about AssignmentHolder-typed focus: + *
    + *
  1. inbounds (for FocusType),
  2. + *
  3. activation before object template (for FocusType),
  4. + *
  5. object template before assignments,
  6. + *
  7. activation after object template (for FocusType),
  8. + *
  9. assignments (including processing orgs, membership/delegate refs, conflicts),
  10. + *
  11. focus lifecycle,
  12. + *
  13. object template after assignments,
  14. + *
  15. activation after second object template (for FocusType),
  16. + *
  17. credentials (for FocusType),
  18. + *
  19. focus policy rules.
  20. + *
+ * + * All of this is executed with regard to iteration. See also {@link IterationHelper}. + * Also takes care for minor things like item limitations * * @author Radovan Semancik * */ @Component -public class AssignmentHolderProcessor { +@ProcessorExecution(focusRequired = true, focusType = AssignmentHolderType.class) +public class AssignmentHolderProcessor implements ProjectorProcessor { private static final Trace LOGGER = TraceManager.getTrace(AssignmentHolderProcessor.class); - @Autowired private ContextLoader contextLoader; @Autowired private InboundProcessor inboundProcessor; @Autowired private AssignmentProcessor assignmentProcessor; @Autowired private ObjectTemplateProcessor objectTemplateProcessor; @@ -73,426 +68,168 @@ public class AssignmentHolderProcessor { @Autowired private PolicyRuleProcessor policyRuleProcessor; @Autowired private FocusLifecycleProcessor focusLifecycleProcessor; @Autowired private ClockworkMedic medic; - @Autowired private PolicyRuleEnforcer policyRuleEnforcer; @Autowired private CacheConfigurationManager cacheConfigurationManager; - - @Autowired private FocusProcessor focusProcessor; + @Autowired private CredentialsProcessor credentialsProcessor; + @Autowired private ItemLimitationsChecker itemLimitationsChecker; + @Autowired private FocusActivationProcessor focusActivationProcessor; @Autowired @Qualifier("cacheRepositoryService") private transient RepositoryService cacheRepositoryService; - public void processFocus(LensContext context, String activityDescription, - XMLGregorianCalendar now, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, PolicyViolationException, - ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException, PreconditionViolationException { - - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - return; - } - - if (!AssignmentHolderType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - LOGGER.trace("Skipping processing focus. Not assignment holder type, type {}", focusContext.getObjectTypeClass()); - return; - } + @ProcessorMethod + public void processFocus(LensContext context, String activityDescription, XMLGregorianCalendar now, + Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ConfigurationException, + CommunicationException, SecurityViolationException, PolicyViolationException, ObjectAlreadyExistsException, + PreconditionViolationException { - //noinspection unchecked - processFocusFocus((LensContext)context, activityDescription, now, task, result); - - } - - @SuppressWarnings({ "unused", "RedundantThrows" }) - private void processFocusNonFocus(LensContext context, String activityDescription, - XMLGregorianCalendar now, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, PolicyViolationException, - ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException, PreconditionViolationException { - // This is somehow "future legacy" code. It will be removed later when we have better support for organizational structure - // membership in resources and tasks. - assignmentProcessor.computeTenantRefLegacy(context, task, result); - } - - private void processFocusFocus(LensContext context, String activityDescription, - XMLGregorianCalendar now, Task task, OperationResult result) - throws ObjectNotFoundException, SchemaException, ExpressionEvaluationException, PolicyViolationException, - ObjectAlreadyExistsException, CommunicationException, ConfigurationException, SecurityViolationException, PreconditionViolationException { LensFocusContext focusContext = context.getFocusContext(); PartialProcessingOptionsType partialProcessingOptions = context.getPartialProcessingOptions(); - checkArchetypeRefDelta(context); - - boolean resetOnRename = true; // This is fixed now. TODO: make it configurable - - boolean wasResetOnIterationSpecificationChange = false; - boolean iterationSpecificationInitialized = false; - IterationSpecificationType iterationSpecification = null; // initializing just for the compiler not to complain - int maxIterations = 0; // initializing just for the compiler not to complain - - int iteration = focusContext.getIteration(); - String iterationToken = focusContext.getIterationToken(); - boolean wasResetIterationCounter = false; - - PrismObject focusCurrent = focusContext.getObjectCurrent(); - if (focusCurrent != null && iterationToken == null) { - Integer focusIteration = focusCurrent.asObjectable().getIteration(); - if (focusIteration != null) { - iteration = focusIteration; - } - iterationToken = focusCurrent.asObjectable().getIterationToken(); - } + IterationHelper iterationHelper = new IterationHelper<>(this, context, focusContext); while (true) { - ObjectTemplateType objectTemplate = context.getFocusTemplate(); - - if (!iterationSpecificationInitialized) { - iterationSpecification = LensUtil.getIterationSpecification(objectTemplate); - maxIterations = LensUtil.determineMaxIterations(iterationSpecification); - LOGGER.trace("maxIterations = {}, iteration specification = {} derived from template {}", maxIterations, - iterationSpecification, objectTemplate); - iterationSpecificationInitialized = true; - } - ArchetypePolicyType archetypePolicy = focusContext.getArchetypePolicyType(); LensUtil.applyObjectPolicyConstraints(focusContext, archetypePolicy, prismContext); - ExpressionVariables variablesPreIteration = ModelImplUtils.getDefaultExpressionVariables(focusContext.getObjectNew(), - null, null, null, context.getSystemConfiguration(), focusContext, prismContext); - if (iterationToken == null) { - iterationToken = LensUtil.formatIterationToken(context, focusContext, - iterationSpecification, iteration, expressionFactory, variablesPreIteration, task, result); - } + iterationHelper.onIterationStart(task, result); - // We have to remember the token and iteration in the context. - // The context can be recomputed several times. But we always want - // to use the same iterationToken if possible. If there is a random - // part in the iterationToken expression that we need to avoid recomputing - // the token otherwise the value can change all the time (even for the same inputs). - // Storing the token in the secondary delta is not enough because secondary deltas can be dropped - // if the context is re-projected. - focusContext.setIteration(iteration); - focusContext.setIterationToken(iterationToken); - LOGGER.trace("Focus {} processing, iteration {}, token '{}'", focusContext.getHumanReadableName(), iteration, iterationToken); - - String conflictMessage; - if (!LensUtil.evaluateIterationCondition(context, focusContext, - iterationSpecification, iteration, iterationToken, true, expressionFactory, variablesPreIteration, task, result)) { - - conflictMessage = "pre-iteration condition was false"; - LOGGER.debug("Skipping iteration {}, token '{}' for {} because the pre-iteration condition was false", - iteration, iterationToken, focusContext.getHumanReadableName()); - } else { + if (iterationHelper.doesPreIterationConditionHold(task, result)) { // INBOUND - if (consistencyChecks) context.checkConsistence(); - - medic.partialExecute("inbound", - (result1) -> { - // Loop through the account changes, apply inbound expressions - inboundProcessor.processInbound(context, now, task, result1); - if (consistencyChecks) context.checkConsistence(); - context.recomputeFocus(); - contextLoader.updateArchetypePolicy(context, task, result1); - contextLoader.updateArchetype(context, task, result1); - medic.traceContext(LOGGER, activityDescription, "inbound", false, context, false); - if (consistencyChecks) context.checkConsistence(); - }, - partialProcessingOptions::getInbound, - Projector.class, context, result); + context.checkConsistenceIfNeeded(); + + boolean inboundRun = + medic.partialExecute(Components.INBOUND, inboundProcessor, + inboundProcessor::processInbounds, + partialProcessingOptions::getInbound, + Projector.class, context, activityDescription, now, task, result); + + if (inboundRun && !focusContext.isDelete() && iterationHelper.didIterationSpecificationChange()) { + cleanupContext(focusContext); + continue; + } // ACTIVATION - medic.partialExecute("focusActivation", - (result1) -> focusProcessor.processActivationBeforeAssignments(context, now, result1), + medic.partialExecute(Components.FOCUS_ACTIVATION, focusActivationProcessor, + focusActivationProcessor::processActivationBeforeObjectTemplate, partialProcessingOptions::getFocusActivation, - Projector.class, context, result); - + Projector.class, context, now, task, result); // OBJECT TEMPLATE (before assignments) - if (focusContext.isDelete()) { - LOGGER.trace("Skipping refreshing of object template and iterator specification: focus delete"); - } else { - contextLoader.setFocusTemplate(context, result); - if (!wasResetOnIterationSpecificationChange) { - IterationSpecificationType newIterationSpecification = context.getFocusTemplate() != null ? - context.getFocusTemplate().getIterationSpecification() : null; - if (!java.util.Objects.equals(iterationSpecification, newIterationSpecification)) { - LOGGER.trace("Resetting iteration counter and token because of iteration specification change"); - iteration = 0; - iterationToken = null; - wasResetOnIterationSpecificationChange = true; - wasResetIterationCounter = false; - iterationSpecificationInitialized = false; - cleanupContext(focusContext); - continue; - } - } - } - - medic.partialExecute("objectTemplateBeforeAssignments", - (result1) -> objectTemplateProcessor.processTemplate(context, - ObjectTemplateMappingEvaluationPhaseType.BEFORE_ASSIGNMENTS, now, task, result1), + medic.partialExecute(Components.OBJECT_TEMPLATE_BEFORE_ASSIGNMENTS, objectTemplateProcessor, + objectTemplateProcessor::processTemplateBeforeAssignments, partialProcessingOptions::getObjectTemplateBeforeAssignments, - Projector.class, context, result); + Projector.class, context, now, task, result); + // Process activation again. Object template might have changed it. - // process activation again. Object template might have changed it. - context.recomputeFocus(); - medic.partialExecute("focusActivation", - (result1) -> focusProcessor.processActivationAfterAssignments(context, now, result1), + medic.partialExecute(Components.FOCUS_ACTIVATION, focusActivationProcessor, + focusActivationProcessor::processActivationAfterObjectTemplate, partialProcessingOptions::getFocusActivation, - Projector.class, context, result); + Projector.class, context, now, task, result); // ASSIGNMENTS focusContext.clearPendingObjectPolicyStateModifications(); focusContext.clearPendingAssignmentPolicyStateModifications(); - medic.partialExecute("assignments", - (result1) -> assignmentProcessor.processAssignments(context, now, task, result1), + medic.partialExecute(Components.ASSIGNMENTS, assignmentProcessor, + assignmentProcessor::processAssignments, partialProcessingOptions::getAssignments, - Projector.class, context, result); + Projector.class, context, now, task, result); - medic.partialExecute("assignmentsOrg", - (result1) -> assignmentProcessor.processOrgAssignments(context, task, result1), + medic.partialExecute(Components.ASSIGNMENTS_ORG, assignmentProcessor, + assignmentProcessor::processOrgAssignments, partialProcessingOptions::getAssignmentsOrg, - Projector.class, context, result); - + Projector.class, context, now, task, result); - medic.partialExecute("assignmentsMembershipAndDelegate", - (result1) -> assignmentProcessor.processMembershipAndDelegatedRefs(context, result1), + medic.partialExecute(Components.ASSIGNMENTS_MEMBERSHIP_AND_DELEGATE, assignmentProcessor, + assignmentProcessor::processMembershipAndDelegatedRefs, partialProcessingOptions::getAssignmentsMembershipAndDelegate, - Projector.class, context, result); - - context.recompute(); + Projector.class, context, now, task, result); - medic.partialExecute("assignmentsConflicts", - (result1) -> assignmentProcessor.checkForAssignmentConflicts(context, result1), + medic.partialExecute(Components.ASSIGNMENTS_CONFLICTS, assignmentProcessor, + assignmentProcessor::checkForAssignmentConflicts, partialProcessingOptions::getAssignmentsConflicts, - Projector.class, context, result); + Projector.class, context, now, task, result); - medic.partialExecute("focusLifecycle", - (result1) -> focusLifecycleProcessor.processLifecycle(context, now, task, result1), + medic.partialExecute(Components.FOCUS_LIFECYCLE, focusLifecycleProcessor, + focusLifecycleProcessor::process, partialProcessingOptions::getFocusLifecycle, - Projector.class, context, result); + Projector.class, context, now, task, result); // OBJECT TEMPLATE (after assignments) - medic.partialExecute("objectTemplateAfterAssignments", - (result1) -> objectTemplateProcessor.processTemplate(context, - ObjectTemplateMappingEvaluationPhaseType.AFTER_ASSIGNMENTS, now, task, result1), - partialProcessingOptions::getObjectTemplateBeforeAssignments, - Projector.class, context, result); + medic.partialExecute(Components.OBJECT_TEMPLATE_AFTER_ASSIGNMENTS, objectTemplateProcessor, + objectTemplateProcessor::processTemplateAfterAssignments, + partialProcessingOptions::getObjectTemplateAfterAssignments, + Projector.class, context, now, task, result); - context.recompute(); + // Process activation again. Second pass through object template might have changed it. + // We also need to apply assignment activation if needed. - // process activation again. Second pass through object template might have changed it. - // We also need to apply assignment activation if needed - context.recomputeFocus(); - medic.partialExecute("focusActivation", - (result1) -> focusProcessor.processActivationAfterAssignments(context, now, result1), + medic.partialExecute(Components.FOCUS_ACTIVATION, focusActivationProcessor, + focusActivationProcessor::processActivationAfterAssignments, partialProcessingOptions::getFocusActivation, - Projector.class, context, result); + Projector.class, context, now, task, result); // CREDENTIALS (including PASSWORD POLICY) - medic.partialExecute("focusCredentials", - (result1) -> focusProcessor.processCredentials(context, now, task, result1), + medic.partialExecute(Components.FOCUS_CREDENTIALS, credentialsProcessor, + credentialsProcessor::processFocusCredentials, partialProcessingOptions::getFocusCredentials, - Projector.class, context, result); + Projector.class, context, now, task, result); // We need to evaluate this as a last step. We need to make sure we have all the // focus deltas so we can properly trigger the rules. - medic.partialExecute("focusPolicyRules", - (result1) -> policyRuleProcessor.evaluateObjectPolicyRules(context, activityDescription, now, task, result1), + medic.partialExecute(Components.FOCUS_POLICY_RULES, policyRuleProcessor, + policyRuleProcessor::evaluateObjectPolicyRules, partialProcessingOptions::getFocusPolicyRules, - Projector.class, context, result); - - // to mimic operation of the original enforcer hook, we execute the following only in the initial state - if (context.getState() == ModelState.INITIAL) { - // If partial execution for focus policy rules and for assignments is turned off, this method call is a no-op. - // So we don't need to check the partial execution flags for its invocation. - policyRuleEnforcer.execute(context); - } + Projector.class, context, now, task, result); // Processing done, check for success - //noinspection ConstantConditions - if (resetOnRename && !wasResetIterationCounter && willResetIterationCounter(focusContext)) { - // Make sure this happens only the very first time during the first recompute. - // Otherwise it will always change the token (especially if the token expression has a random part) - // hence the focusContext.getIterationToken() == null - wasResetIterationCounter = true; - if (iteration != 0) { - iteration = 0; - iterationToken = null; - LOGGER.trace("Resetting iteration counter and token because rename was detected"); - cleanupContext(focusContext); - continue; - } - } - - ConstraintsCheckingStrategyType strategy = context.getFocusConstraintsCheckingStrategy(); - boolean skipWhenNoChange = strategy != null && Boolean.TRUE.equals(strategy.isSkipWhenNoChange()); - boolean skipWhenNoIteration = strategy != null && Boolean.TRUE.equals(strategy.isSkipWhenNoIteration()); - - boolean checkConstraints; - if (skipWhenNoChange && !hasNameDelta(focusContext)) { - LOGGER.trace("Skipping constraints check because 'skipWhenNoChange' is true and there's no name delta"); - checkConstraints = false; - } else if (skipWhenNoIteration && maxIterations == 0) { - LOGGER.trace("Skipping constraints check because 'skipWhenNoIteration' is true and there is no iteration defined"); - checkConstraints = false; - } else if (TaskType.class == focusContext.getObjectTypeClass()) { - LOGGER.trace("Skipping constraints check for task, not needed because tasks names are not unique."); - checkConstraints = false; - } else { - checkConstraints = true; - } - - PrismObject previewObjectNew = focusContext.getObjectNew(); - if (previewObjectNew == null) { - // this must be delete - } else { - // Explicitly check for name. The checker would check for this also. But checking it here - // will produce better error message - PolyStringType objectName = previewObjectNew.asObjectable().getName(); - if (objectName == null || objectName.getOrig().isEmpty()) { - throw new NoFocusNameSchemaException("No name in new object "+objectName+" as produced by template "+objectTemplate+ - " in iteration "+iteration+", we cannot process an object without a name"); - } + if (iterationHelper.didResetOnRenameOccur()) { + cleanupContext(focusContext); + continue; } - // Check if iteration constraints are OK - FocusConstraintsChecker checker = new FocusConstraintsChecker<>(); - checker.setPrismContext(prismContext); - checker.setContext(context); - checker.setRepositoryService(cacheRepositoryService); - checker.setCacheConfigurationManager(cacheConfigurationManager); - boolean satisfies = !checkConstraints || checker.check(previewObjectNew, result); - if (satisfies) { - LOGGER.trace("Current focus satisfies uniqueness constraints. Iteration {}, token '{}'", iteration, iterationToken); - ExpressionVariables variablesPostIteration = ModelImplUtils.getDefaultExpressionVariables(focusContext.getObjectNew(), - null, null, null, context.getSystemConfiguration(), focusContext, prismContext); - if (LensUtil.evaluateIterationCondition(context, focusContext, - iterationSpecification, iteration, iterationToken, false, expressionFactory, variablesPostIteration, - task, result)) { - // stop the iterations - break; - } else { - conflictMessage = "post-iteration condition was false"; - LOGGER.debug("Skipping iteration {}, token '{}' for {} because the post-iteration condition was false", - iteration, iterationToken, focusContext.getHumanReadableName()); - } - } else { - LOGGER.trace("Current focus does not satisfy constraints. Conflicting object: {}; iteration={}, maxIterations={}", - checker.getConflictingObject(), iteration, maxIterations); - conflictMessage = checker.getMessages(); + PrismObject objectNew = focusContext.getObjectNew(); + if (objectNew == null) { + LOGGER.trace("Object delete: skipping name presence and uniqueness check"); + break; } - if (!wasResetIterationCounter) { - wasResetIterationCounter = true; - if (iteration != 0) { - iterationToken = null; - iteration = 0; - LOGGER.trace("Resetting iteration counter and token after conflict"); - cleanupContext(focusContext); - continue; - } + if (iterationHelper.isIterationOk(objectNew, task, result)) { + break; } - } - - // Next iteration - iteration++; - iterationToken = null; - LensUtil.checkMaxIterations(iteration, maxIterations, conflictMessage, focusContext.getHumanReadableName()); - cleanupContext(focusContext); - } - addIterationTokenDeltas(focusContext, iteration, iterationToken); - checkItemsLimitations(focusContext); - if (consistencyChecks) context.checkConsistence(); - - } - - private void checkItemsLimitations(LensFocusContext focusContext) - throws SchemaException, ConfigurationException { - Map itemDefinitionsMap = focusContext.getItemDefinitionsMap(); - PrismObject objectNew = null; // lazily evaluated - for (Map.Entry entry : itemDefinitionsMap.entrySet()) { - for (PropertyLimitationsType limitation : entry.getValue().getLimitations()) { - if (!limitation.getLayer().contains(LayerType.MODEL)) { // or should we apply SCHEMA-layer limitations as well? + if (iterationHelper.shouldResetOnConflict()) { + cleanupContext(focusContext); continue; } - if (objectNew == null) { - focusContext.recompute(); - objectNew = focusContext.getObjectNew(); - if (objectNew == null) { - return; // nothing to check on DELETE operation - } - } - checkItemLimitations(objectNew, entry.getKey(), limitation); } - } - } - - private void checkItemLimitations(PrismObject object, ItemPath path, PropertyLimitationsType limitation) - throws SchemaException { - Object item = object.find(path); - if (isTrue(limitation.isIgnore())) { - return; - } - int count = getValueCount(item); - Integer min = DefinitionUtil.parseMultiplicity(limitation.getMinOccurs()); - if (min != null && min > 0 && count < min) { - throw new SchemaException("Expected at least " + min + " values of " + path + ", got " + count); - } - Integer max = DefinitionUtil.parseMultiplicity(limitation.getMaxOccurs()); - if (max != null && max >= 0 && count > max) { - throw new SchemaException("Expected at most " + max + " values of " + path + ", got " + count); - } - } - private int getValueCount(Object item) { - if (item == null) { - return 0; - } - if (!(item instanceof Item)) { - throw new IllegalStateException("Expected Item but got " + item.getClass() + " instead"); + iterationHelper.incrementIterationCounter(); + cleanupContext(focusContext); } - return ((Item) item).getValues().size(); - } + iterationHelper.createIterationTokenDeltas(); + context.recomputeFocus(); + itemLimitationsChecker.checkItemsLimitations(focusContext); + context.checkConsistenceIfNeeded(); - private boolean willResetIterationCounter(LensFocusContext focusContext) throws SchemaException { - ObjectDelta focusDelta = focusContext.getDelta(); - if (focusDelta == null) { - return false; - } - if (focusContext.isAdd() || focusContext.isDelete()) { - return false; - } - if (focusDelta.findPropertyDelta(FocusType.F_ITERATION) != null) { - // there was a reset already in previous projector runs - return false; - } - // Check for rename - return hasNameDelta(focusDelta); - } - - private boolean hasNameDelta(LensFocusContext focusContext) throws SchemaException { - ObjectDelta focusDelta = focusContext.getDelta(); - return focusDelta != null && hasNameDelta(focusDelta); - } - - private boolean hasNameDelta(ObjectDelta focusDelta) { - PropertyDelta nameDelta = focusDelta.findPropertyDelta(FocusType.F_NAME); - return nameDelta != null; + medic.traceContext(LOGGER, activityDescription, "focus processing", false, context, false); + LensUtil.checkContextSanity(context, "focus processing", result); } /** @@ -508,383 +245,19 @@ private void cleanupContext(LensFocusContext void processActivationBeforeAssignments(LensContext context, XMLGregorianCalendar now, -// OperationResult result) -// throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { -// processActivationBasic(context, now, result); -// } -// -// private void processActivationAfterAssignments(LensContext context, XMLGregorianCalendar now, -// OperationResult result) -// throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { -// processActivationBasic(context, now, result); -// processAssignmentActivation(context, now, result); -// } -// -// private void processActivationBasic(LensContext context, XMLGregorianCalendar now, -// OperationResult result) -// throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { -// LensFocusContext focusContext = context.getFocusContext(); -// -// if (focusContext.isDelete()) { -// LOGGER.trace("Skipping processing of focus activation: focus delete"); -// return; -// } -// -// processActivationAdministrativeAndValidity(focusContext, now, result); -// -// if (focusContext.canRepresent(UserType.class)) { -// processActivationLockout((LensFocusContext) focusContext, now, result); -// } -// } -// -// private void processActivationAdministrativeAndValidity(LensFocusContext focusContext, XMLGregorianCalendar now, -// OperationResult result) -// throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { -// -// TimeIntervalStatusType validityStatusNew = null; -// TimeIntervalStatusType validityStatusCurrent = null; -// XMLGregorianCalendar validityChangeTimestamp = null; -// -// String lifecycleStateNew = null; -// String lifecycleStateCurrent = null; -// ActivationType activationNew = null; -// ActivationType activationCurrent = null; -// -// PrismObject focusNew = focusContext.getObjectNew(); -// if (focusNew != null) { -// F focusTypeNew = focusNew.asObjectable(); -// activationNew = focusTypeNew.getActivation(); -// if (activationNew != null) { -// validityStatusNew = activationComputer.getValidityStatus(activationNew, now); -// validityChangeTimestamp = activationNew.getValidityChangeTimestamp(); -// } -// lifecycleStateNew = focusTypeNew.getLifecycleState(); -// } -// -// PrismObject focusCurrent = focusContext.getObjectCurrent(); -// if (focusCurrent != null) { -// F focusCurrentType = focusCurrent.asObjectable(); -// activationCurrent = focusCurrentType.getActivation(); -// if (activationCurrent != null) { -// validityStatusCurrent = activationComputer.getValidityStatus(activationCurrent, validityChangeTimestamp); -// } -// lifecycleStateCurrent = focusCurrentType.getLifecycleState(); -// } -// -// if (validityStatusCurrent == validityStatusNew) { -// // No change, (almost) no work -// if (validityStatusNew != null && activationNew.getValidityStatus() == null) { -// // There was no validity change. But the status is not recorded. So let's record it so it can be used in searches. -// recordValidityDelta(focusContext, validityStatusNew, now); -// } else { -// LOGGER.trace("Skipping validity processing because there was no change ({} -> {})", validityStatusCurrent, validityStatusNew); -// } -// } else { -// LOGGER.trace("Validity change {} -> {}", validityStatusCurrent, validityStatusNew); -// recordValidityDelta(focusContext, validityStatusNew, now); -// } -// -// LifecycleStateModelType lifecycleModel = focusContext.getLifecycleModel(); -// ActivationStatusType effectiveStatusNew = activationComputer.getEffectiveStatus(lifecycleStateNew, activationNew, validityStatusNew, lifecycleModel); -// ActivationStatusType effectiveStatusCurrent = activationComputer.getEffectiveStatus(lifecycleStateCurrent, activationCurrent, validityStatusCurrent, lifecycleModel); -// -// if (effectiveStatusCurrent == effectiveStatusNew) { -// // No change, (almost) no work -// if (effectiveStatusNew != null && (activationNew == null || activationNew.getEffectiveStatus() == null)) { -// // There was no effective status change. But the status is not recorded. So let's record it so it can be used in searches. -// recordEffectiveStatusDelta(focusContext, effectiveStatusNew, now); -// } else { -// if (focusContext.getPrimaryDelta() != null && focusContext.getPrimaryDelta().hasItemDelta(SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS)) { -// LOGGER.trace("Forcing effective status delta even though there was no change ({} -> {}) because there is explicit administrativeStatus delta", effectiveStatusCurrent, effectiveStatusNew); -// // We need this to force the change down to the projections later in the activation processor -// // some of the mappings will use effectiveStatus as a source, therefore there has to be a delta for the mapping to work correctly -// recordEffectiveStatusDelta(focusContext, effectiveStatusNew, now); -// } else { -// //check computed effective status current with the saved one - e.g. there can be some inconsistencies so we need to check and force the change.. in other cases, effectvie status will be stored with -// // incorrect value. Maybe another option is to not compute effectiveStatusCurrent if there is an existing (saved) effective status in the user.. TODO -// if (activationCurrent != null && activationCurrent.getEffectiveStatus() != null) { -// ActivationStatusType effectiveStatusSaved = activationCurrent.getEffectiveStatus(); -// if (effectiveStatusSaved != effectiveStatusNew) { -// recordEffectiveStatusDelta(focusContext, effectiveStatusNew, now); -// } -// } -// LOGGER.trace("Skipping effective status processing because there was no change ({} -> {})", effectiveStatusCurrent, effectiveStatusNew); -// } -// } -// } else { -// LOGGER.trace("Effective status change {} -> {}", effectiveStatusCurrent, effectiveStatusNew); -// recordEffectiveStatusDelta(focusContext, effectiveStatusNew, now); -// } -// -// -// } -// -// private void processActivationLockout(LensFocusContext focusContext, XMLGregorianCalendar now, -// OperationResult result) -// throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { -// -// ObjectDelta focusPrimaryDelta = focusContext.getPrimaryDelta(); -// if (focusPrimaryDelta != null) { -// PropertyDelta lockoutStatusDelta = focusContext.getPrimaryDelta().findPropertyDelta(SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS); -// if (lockoutStatusDelta != null) { -// if (lockoutStatusDelta.isAdd()) { -// for (PrismPropertyValue pval: lockoutStatusDelta.getValuesToAdd()) { -// if (pval.getValue() == LockoutStatusType.LOCKED) { -// throw new SchemaException("Lockout status cannot be changed to LOCKED value"); -// } -// } -// } else if (lockoutStatusDelta.isReplace()) { -// for (PrismPropertyValue pval: lockoutStatusDelta.getValuesToReplace()) { -// if (pval.getValue() == LockoutStatusType.LOCKED) { -// throw new SchemaException("Lockout status cannot be changed to LOCKED value"); -// } -// } -// } -// } -// } -// -// ActivationType activationNew = null; -// ActivationType activationCurrent = null; -// -// LockoutStatusType lockoutStatusNew = null; -// LockoutStatusType lockoutStatusCurrent = null; -// -// PrismObject focusNew = focusContext.getObjectNew(); -// if (focusNew != null) { -// activationNew = focusNew.asObjectable().getActivation(); -// if (activationNew != null) { -// lockoutStatusNew = activationNew.getLockoutStatus(); -// } -// } -// -// PrismObject focusCurrent = focusContext.getObjectCurrent(); -// if (focusCurrent != null) { -// activationCurrent = focusCurrent.asObjectable().getActivation(); -// if (activationCurrent != null) { -// lockoutStatusCurrent = activationCurrent.getLockoutStatus(); -// } -// } -// -// if (lockoutStatusNew == lockoutStatusCurrent) { -// // No change, (almost) no work -// LOGGER.trace("Skipping lockout processing because there was no change ({} -> {})", lockoutStatusCurrent, lockoutStatusNew); -// return; -// } -// -// LOGGER.trace("Lockout change {} -> {}", lockoutStatusCurrent, lockoutStatusNew); -// -// if (lockoutStatusNew == LockoutStatusType.NORMAL) { -// -// CredentialsType credentialsTypeNew = focusNew.asObjectable().getCredentials(); -// if (credentialsTypeNew != null) { -// resetFailedLogins(focusContext, credentialsTypeNew.getPassword(), SchemaConstants.PATH_CREDENTIALS_PASSWORD_FAILED_LOGINS); -// resetFailedLogins(focusContext, credentialsTypeNew.getNonce(), SchemaConstants.PATH_CREDENTIALS_NONCE_FAILED_LOGINS); -// resetFailedLogins(focusContext, credentialsTypeNew.getSecurityQuestions(), SchemaConstants.PATH_CREDENTIALS_SECURITY_QUESTIONS_FAILED_LOGINS); -// } -// -// if (activationNew != null && activationNew.getLockoutExpirationTimestamp() != null) { -// PrismContainerDefinition activationDefinition = getActivationDefinition(); -// PrismPropertyDefinition lockoutExpirationTimestampDef = activationDefinition.findPropertyDefinition(ActivationType.F_LOCKOUT_EXPIRATION_TIMESTAMP); -// PropertyDelta lockoutExpirationTimestampDelta -// = lockoutExpirationTimestampDef.createEmptyDelta(ItemPath.create(UserType.F_ACTIVATION, ActivationType.F_LOCKOUT_EXPIRATION_TIMESTAMP)); -// lockoutExpirationTimestampDelta.setValueToReplace(); -// focusContext.swallowToProjectionWaveSecondaryDelta(lockoutExpirationTimestampDelta); -// } -// } -// -// } -// -// private void resetFailedLogins(LensFocusContext focusContext, AbstractCredentialType credentialTypeNew, ItemPath path) throws SchemaException{ -// if (credentialTypeNew != null) { -// Integer failedLogins = credentialTypeNew.getFailedLogins(); -// if (failedLogins != null && failedLogins != 0) { -// PrismPropertyDefinition failedLoginsDef = getFailedLoginsDefinition(); -// PropertyDelta failedLoginsDelta = failedLoginsDef.createEmptyDelta(path); -// failedLoginsDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(0, OriginType.USER_POLICY, null)); -// focusContext.swallowToProjectionWaveSecondaryDelta(failedLoginsDelta); -// } -// } -// } -// -// private void recordValidityDelta(LensFocusContext focusContext, TimeIntervalStatusType validityStatusNew, -// XMLGregorianCalendar now) throws SchemaException { -// PrismContainerDefinition activationDefinition = getActivationDefinition(); -// -// PrismPropertyDefinition validityStatusDef = activationDefinition.findPropertyDefinition(ActivationType.F_VALIDITY_STATUS); -// PropertyDelta validityStatusDelta -// = validityStatusDef.createEmptyDelta(ItemPath.create(UserType.F_ACTIVATION, ActivationType.F_VALIDITY_STATUS)); -// if (validityStatusNew == null) { -// validityStatusDelta.setValueToReplace(); -// } else { -// validityStatusDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(validityStatusNew, OriginType.USER_POLICY, null)); -// } -// focusContext.swallowToProjectionWaveSecondaryDelta(validityStatusDelta); -// -// PrismPropertyDefinition validityChangeTimestampDef = activationDefinition.findPropertyDefinition(ActivationType.F_VALIDITY_CHANGE_TIMESTAMP); -// PropertyDelta validityChangeTimestampDelta -// = validityChangeTimestampDef.createEmptyDelta(ItemPath.create(UserType.F_ACTIVATION, ActivationType.F_VALIDITY_CHANGE_TIMESTAMP)); -// validityChangeTimestampDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(now, OriginType.USER_POLICY, null)); -// focusContext.swallowToProjectionWaveSecondaryDelta(validityChangeTimestampDelta); -// } -// -// private void recordEffectiveStatusDelta(LensFocusContext focusContext, -// ActivationStatusType effectiveStatusNew, XMLGregorianCalendar now) -// throws SchemaException { -// PrismContainerDefinition activationDefinition = getActivationDefinition(); -// -// // We always want explicit delta for effective status even if there is no real change -// // we want to propagate enable/disable events to all the resources, even if we are enabling -// // already enabled user (some resources may be disabled) -// // This may produce duplicate delta, but that does not matter too much. The duplicate delta -// // will be filtered out later. -// PrismPropertyDefinition effectiveStatusDef = activationDefinition.findPropertyDefinition(ActivationType.F_EFFECTIVE_STATUS); -// PropertyDelta effectiveStatusDelta -// = effectiveStatusDef.createEmptyDelta(PATH_ACTIVATION_EFFECTIVE_STATUS); -// effectiveStatusDelta.setValueToReplace(prismContext.itemFactory().createPropertyValue(effectiveStatusNew, OriginType.USER_POLICY, null)); -// if (!focusContext.alreadyHasDelta(effectiveStatusDelta)){ -// focusContext.swallowToProjectionWaveSecondaryDelta(effectiveStatusDelta); -// } -// -// // It is not enough to check alreadyHasDelta(). The change may happen in previous waves -// // and the secondary delta may no longer be here. When it comes to disableTimestamp we even -// // cannot rely on natural filtering of already executed deltas as the timestamp here may -// // be off by several milliseconds. So explicitly check for the change here. -// PrismObject objectCurrent = focusContext.getObjectCurrent(); -// if (objectCurrent != null) { -// PrismProperty effectiveStatusPropCurrent = objectCurrent.findProperty(PATH_ACTIVATION_EFFECTIVE_STATUS); -// if (effectiveStatusPropCurrent != null && effectiveStatusNew.equals(effectiveStatusPropCurrent.getRealValue())) { -// LOGGER.trace("Skipping setting disableTimestamp because there was no change"); -// return; -// } -// } -// -// PropertyDelta timestampDelta = LensUtil.createActivationTimestampDelta(effectiveStatusNew, now, activationDefinition, OriginType.USER_POLICY, -// prismContext); -// if (!focusContext.alreadyHasDelta(timestampDelta)) { -// focusContext.swallowToProjectionWaveSecondaryDelta(timestampDelta); -// } -// } -// -// -// private PrismContainerDefinition getActivationDefinition() { -// if (activationDefinition == null) { -// ComplexTypeDefinition focusDefinition = prismContext.getSchemaRegistry().findComplexTypeDefinition(FocusType.COMPLEX_TYPE); -// activationDefinition = focusDefinition.findContainerDefinition(FocusType.F_ACTIVATION); -// } -// return activationDefinition; -// } -// -// private PrismPropertyDefinition getFailedLoginsDefinition() { -// if (failedLoginsDefinition == null) { -// PrismObjectDefinition userDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(UserType.class); -// failedLoginsDefinition = userDef.findPropertyDefinition(SchemaConstants.PATH_CREDENTIALS_PASSWORD_FAILED_LOGINS); -// } -// return failedLoginsDefinition; -// } - - /** - * Adds deltas for iteration and iterationToken to the focus if needed. - */ - private void addIterationTokenDeltas(LensFocusContext focusContext, int iteration, String iterationToken) throws SchemaException { - PrismObject objectCurrent = focusContext.getObjectCurrent(); - if (objectCurrent != null) { - Integer iterationOld = objectCurrent.asObjectable().getIteration(); - String iterationTokenOld = objectCurrent.asObjectable().getIterationToken(); - if (iterationOld != null && iterationOld == iteration && - iterationTokenOld != null && iterationTokenOld.equals(iterationToken)) { - // Already stored - return; - } - } - PrismObjectDefinition objDef = focusContext.getObjectDefinition(); - - PrismPropertyValue iterationVal = prismContext.itemFactory().createPropertyValue(iteration); - iterationVal.setOriginType(OriginType.USER_POLICY); - PropertyDelta iterationDelta = prismContext.deltaFactory().property().createReplaceDelta(objDef, - FocusType.F_ITERATION, iterationVal); - focusContext.swallowToSecondaryDelta(iterationDelta); - - PrismPropertyValue iterationTokenVal = prismContext.itemFactory().createPropertyValue(iterationToken); - iterationTokenVal.setOriginType(OriginType.USER_POLICY); - PropertyDelta iterationTokenDelta = prismContext.deltaFactory().property().createReplaceDelta(objDef, - FocusType.F_ITERATION_TOKEN, iterationTokenVal); - focusContext.swallowToSecondaryDelta(iterationTokenDelta); - + ExpressionFactory getExpressionFactory() { + return expressionFactory; } - private void checkArchetypeRefDelta(LensContext context) throws PolicyViolationException { - ObjectDelta focusPrimaryDelta = context.getFocusContext().getPrimaryDelta(); - if (focusPrimaryDelta != null) { - ReferenceDelta archetypeRefDelta = focusPrimaryDelta.findReferenceModification(AssignmentHolderType.F_ARCHETYPE_REF); - if (archetypeRefDelta != null) { - // We want to allow this under special circumstances. E.g. we want be able to import user with archetypeRef. - // Otherwise we won't be able to export a user and re-import it again. - if (focusPrimaryDelta.isAdd()) { - String archetypeOidFromAssignments = LensUtil.determineExplicitArchetypeOidFromAssignments(focusPrimaryDelta.getObjectToAdd()); - if (archetypeOidFromAssignments == null) { - throw new PolicyViolationException("Attempt add archetypeRef without a matching assignment"); - } else { - boolean match = true; - for (PrismReferenceValue archetypeRefDeltaVal : archetypeRefDelta.getValuesToAdd()) { - if (!archetypeOidFromAssignments.equals(archetypeRefDeltaVal.getOid())) { - match = false; - } - } - if (match) { - return; - } else { - throw new PolicyViolationException("Attempt add archetypeRef that does not match assignment"); - } - } - } - throw new PolicyViolationException("Attempt to modify archetypeRef directly"); - } - } + PrismContext getPrismContext() { + return prismContext; } -// private void processAssignmentActivation(LensContext context, XMLGregorianCalendar now, -// OperationResult result) throws SchemaException { -// DeltaSetTriple> evaluatedAssignmentTriple = context.getEvaluatedAssignmentTriple(); -// if (evaluatedAssignmentTriple == null) { -// // Code path that should not normally happen. But is used in some tests and may -// // happen during partial processing. -// return; -// } -// // We care only about existing assignments here. New assignments will be taken care of in the executor -// // (OperationalDataProcessor). And why care about deleted assignments? -// Collection> zeroSet = evaluatedAssignmentTriple.getZeroSet(); -// if (zeroSet == null) { -// return; -// } -// LensFocusContext focusContext = context.getFocusContext(); -// for (EvaluatedAssignmentImpl evaluatedAssignment: zeroSet) { -// if (evaluatedAssignment.isVirtual()) { -// continue; -// } -// AssignmentType assignmentType = evaluatedAssignment.getAssignmentType(); -// ActivationType currentActivationType = assignmentType.getActivation(); -// ActivationStatusType expectedEffectiveStatus = activationComputer.getEffectiveStatus(assignmentType.getLifecycleState(), currentActivationType, null); -// if (currentActivationType == null) { -// PrismContainerDefinition activationDef = focusContext.getObjectDefinition().findContainerDefinition(SchemaConstants.PATH_ASSIGNMENT_ACTIVATION); -// ContainerDelta activationDelta = activationDef.createEmptyDelta( -// ItemPath.create(FocusType.F_ASSIGNMENT, assignmentType.getId(), AssignmentType.F_ACTIVATION)); -// ActivationType newActivationType = new ActivationType(); -// activationDelta.setValuesToReplace(newActivationType.asPrismContainerValue()); -// newActivationType.setEffectiveStatus(expectedEffectiveStatus); -// focusContext.swallowToSecondaryDelta(activationDelta); -// } else { -// ActivationStatusType currentEffectiveStatus = currentActivationType.getEffectiveStatus(); -// if (!expectedEffectiveStatus.equals(currentEffectiveStatus)) { -// PrismPropertyDefinition effectiveStatusPropertyDef = focusContext.getObjectDefinition().findPropertyDefinition(SchemaConstants.PATH_ASSIGNMENT_ACTIVATION_EFFECTIVE_STATUS); -// PropertyDelta effectiveStatusDelta = effectiveStatusPropertyDef.createEmptyDelta( -// ItemPath.create(FocusType.F_ASSIGNMENT, assignmentType.getId(), AssignmentType.F_ACTIVATION, ActivationType.F_EFFECTIVE_STATUS)); -// effectiveStatusDelta.setRealValuesToReplace(expectedEffectiveStatus); -// focusContext.swallowToSecondaryDelta(effectiveStatusDelta); -// } -// } -// } -// } - + RepositoryService getCacheRepositoryService() { + return cacheRepositoryService; + } + CacheConfigurationManager getCacheConfigurationManager() { + return cacheConfigurationManager; + } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentProcessor.java index 4830a18b583..0e2e3b59ad3 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentProcessor.java @@ -16,8 +16,11 @@ import com.evolveum.midpoint.model.impl.lens.projector.ComplexConstructionConsumer; import com.evolveum.midpoint.model.impl.lens.projector.ConstructionProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.ProjectorProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; import com.evolveum.midpoint.model.impl.lens.projector.mappings.*; import com.evolveum.midpoint.model.impl.lens.projector.policy.PolicyRuleProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; import com.evolveum.midpoint.model.impl.util.ModelImplUtils; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.*; @@ -89,7 +92,8 @@ * @author Radovan Semancik */ @Component -public class AssignmentProcessor { +@ProcessorExecution(focusRequired = true, focusType = AssignmentHolderType.class) +public class AssignmentProcessor implements ProjectorProcessor { @Autowired @Qualifier("cacheRepositoryService") @@ -118,22 +122,16 @@ public class AssignmentProcessor { /** * Processing all the assignments. */ - @SuppressWarnings("unchecked") + @ProcessorMethod public void processAssignments(LensContext context, XMLGregorianCalendar now, Task task, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - return; - } - if (!AssignmentHolderType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - // We can do this only for AssignmentHolderType. - return; - } + assert context.hasFocusOfType(AssignmentHolderType.class); OperationResult result = parentResult.createSubresult(AssignmentProcessor.class.getName() + ".processAssignments"); try { try { + //noinspection unchecked processAssignmentsProjectionsWithFocus((LensContext) context, now, task, result); } catch (SchemaException | ObjectNotFoundException | ExpressionEvaluationException | PolicyViolationException | CommunicationException | ConfigurationException | SecurityViolationException | RuntimeException | Error e) { @@ -263,7 +261,7 @@ private void processAssig LOGGER.trace("Projection processing done"); - removeIgnoredContexts(context); + context.removeIgnoredContexts(); finishLegalDecisions(context); } else { LOGGER.trace("Skipping evaluating constructions. Not a focus."); @@ -797,12 +795,12 @@ private void createAssignmentDelta(LensContext< } - public void processOrgAssignments(LensContext context, Task task, OperationResult result) throws SchemaException, PolicyViolationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - + @ProcessorMethod + void processOrgAssignments(LensContext context, + @SuppressWarnings("unused") XMLGregorianCalendar now, Task task, + OperationResult result) throws SchemaException, PolicyViolationException, ObjectNotFoundException, + CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - return; - } Collection shouldBeParentOrgRefs = new ArrayList<>(); @@ -894,41 +892,6 @@ private void computeTenantRef(LensContext context, Tas addTenantRefDelta(context, objectNew, tenantOid); } - /** - * This is somehow "future legacy" code. It will be removed later when we have better support for organizational structure - * membership in resources and tasks. - */ - public void computeTenantRefLegacy(LensContext context, Task task, OperationResult result) throws PolicyViolationException, SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { - String tenantOid = null; - - LensFocusContext focusContext = context.getFocusContext(); - PrismObject objectNew = focusContext.getObjectNew(); - if (objectNew == null) { - return; - } - if (!objectNew.canRepresent(ResourceType.class) && !objectNew.canRepresent(TaskType.class)) { - return; - } - - String desc = "parentOrgRef in "+objectNew; - for (ObjectReferenceType parentOrgRef: objectNew.asObjectable().getParentOrgRef()) { - OrgType parentOrg = objectResolver.resolve(parentOrgRef, OrgType.class, null, desc, task, result); - ObjectReferenceType parentTenantRef = parentOrg.getTenantRef(); - if (parentTenantRef == null || parentTenantRef.getOid() == null) { - continue; - } - if (tenantOid == null) { - tenantOid = parentTenantRef.getOid(); - } else { - if (!parentTenantRef.getOid().equals(tenantOid)) { - throw new PolicyViolationException("Two different tenants ("+tenantOid+", "+parentTenantRef.getOid()+") applicable to "+context.getFocusContext().getHumanReadableName()); - } - } - } - - addTenantRefDelta(context, objectNew, tenantOid); - } - private void addTenantRefDelta(LensContext context, PrismObject objectNew, String tenantOid) throws SchemaException { LensFocusContext focusContext = context.getFocusContext(); ObjectReferenceType currentTenantRef = objectNew.asObjectable().getTenantRef(); @@ -956,10 +919,13 @@ private void addTenantRefDelta(LensContext context, Pr } } - public void checkForAssignmentConflicts(LensContext context, - OperationResult result) throws PolicyViolationException, SchemaException { + @ProcessorMethod + void checkForAssignmentConflicts(LensContext context, + @SuppressWarnings("unused") XMLGregorianCalendar now, + @SuppressWarnings("unused") Task task, + @SuppressWarnings("unused") OperationResult result) throws PolicyViolationException, SchemaException { for(LensProjectionContext projectionContext: context.getProjectionContexts()) { - if (AssignmentPolicyEnforcementType.NONE == projectionContext.getAssignmentPolicyEnforcementType()){ + if (AssignmentPolicyEnforcementType.NONE == projectionContext.getAssignmentPolicyEnforcementType()) { continue; } if (projectionContext.isTombstone()) { @@ -1019,20 +985,6 @@ private void markPolicyDecision(LensProjectionContext accountSyncContext, Synchr } } - - - public void removeIgnoredContexts(LensContext context) { - Collection projectionContexts = context.getProjectionContexts(); - Iterator projectionIterator = projectionContexts.iterator(); - while (projectionIterator.hasNext()) { - LensProjectionContext projectionContext = projectionIterator.next(); - - if (projectionContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.IGNORE) { - projectionIterator.remove(); - } - } - } - private XMLGregorianCalendar collectFocusTripleFromMappings( Collection> evaluatedAssignments, Map>> outputTripleMap, @@ -1079,12 +1031,14 @@ private void processMembershipAndDelegatedRefs(LensContext context, - OperationResult result) throws SchemaException, PolicyViolationException, ConfigurationException { + @ProcessorMethod + void processMembershipAndDelegatedRefs(LensContext context, + @SuppressWarnings("unused") XMLGregorianCalendar now, + @SuppressWarnings("unused") Task task, + @SuppressWarnings("unused") OperationResult result) + throws SchemaException, ConfigurationException { + assert context.hasFocusOfType(AssignmentHolderType.class); LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null || !AssignmentHolderType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - return; - } Collection shouldBeRoleRefs = new ArrayList<>(); Collection shouldBeDelegatedRefs = new ArrayList<>(); @@ -1110,6 +1064,8 @@ public void processMembershipAndDelegatedRefs(LensContext setReferences(focusContext, AssignmentHolderType.F_ROLE_MEMBERSHIP_REF, shouldBeRoleRefs); setReferences(focusContext, AssignmentHolderType.F_DELEGATED_REF, shouldBeDelegatedRefs); setReferences(focusContext, AssignmentHolderType.F_ARCHETYPE_REF, shouldBeArchetypeRefs); + + context.recompute(); // really needed? } private void setReferences(LensFocusContext focusContext, QName name, diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusActivationProcessor.java similarity index 82% rename from model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusProcessor.java rename to model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusActivationProcessor.java index e2252689d0d..b197acdb1fc 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusActivationProcessor.java @@ -12,6 +12,12 @@ import javax.xml.datatype.XMLGregorianCalendar; +import com.evolveum.midpoint.model.impl.lens.projector.ProjectorProcessor; + +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; +import com.evolveum.midpoint.model.impl.lens.projector.util.SkipWhenFocusDeleted; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -20,7 +26,6 @@ import com.evolveum.midpoint.model.impl.lens.LensContext; import com.evolveum.midpoint.model.impl.lens.LensFocusContext; import com.evolveum.midpoint.model.impl.lens.LensUtil; -import com.evolveum.midpoint.model.impl.lens.projector.credentials.CredentialsProcessor; import com.evolveum.midpoint.prism.ComplexTypeDefinition; import com.evolveum.midpoint.prism.OriginType; import com.evolveum.midpoint.prism.PrismContainerDefinition; @@ -37,13 +42,7 @@ import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; 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.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.PolicyViolationException; import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.exception.SecurityViolationException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.AbstractCredentialType; @@ -64,73 +63,46 @@ * */ @Component -public class FocusProcessor { +// shouldn't we skip on "secondary delete" as well? +@ProcessorExecution(focusRequired = true, focusType = FocusType.class, skipWhenFocusDeleted = SkipWhenFocusDeleted.PRIMARY) +public class FocusActivationProcessor implements ProjectorProcessor { - private static final Trace LOGGER = TraceManager.getTrace(FocusProcessor.class); + private static final Trace LOGGER = TraceManager.getTrace(FocusActivationProcessor.class); private PrismContainerDefinition activationDefinition; private PrismPropertyDefinition failedLoginsDefinition; @Autowired private PrismContext prismContext; - @Autowired private CredentialsProcessor credentialsProcessor; @Autowired private ActivationComputer activationComputer; - void processActivationBeforeAssignments(LensContext context, XMLGregorianCalendar now, - OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { - - if (isFocus(context)) { - //noinspection unchecked - processActivationBasic((LensContext) context, now, result); - } else { - LOGGER.trace("Skipping activation processing. Not a focus."); - } + @ProcessorMethod + void processActivationBeforeObjectTemplate(LensContext context, XMLGregorianCalendar now, + @SuppressWarnings("unused") Task task, @SuppressWarnings("unused") OperationResult result) throws SchemaException { + processActivationBasic(context, now); } - private boolean isFocus(LensContext context) { - LensFocusContext focusContext = context.getFocusContext(); - return focusContext != null && focusContext.represents(FocusType.class); + @ProcessorMethod + void processActivationAfterObjectTemplate(LensContext context, XMLGregorianCalendar now, + @SuppressWarnings("unused") Task task, @SuppressWarnings("unused") OperationResult result) throws SchemaException { + processActivationBasic(context, now); + processAssignmentActivation(context); // really? } - void processActivationAfterAssignments(LensContext context, XMLGregorianCalendar now, - OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { - - processActivationBeforeAssignments(context, now, result); - - processAssignmentActivation(context, now, result); + @ProcessorMethod + void processActivationAfterAssignments(LensContext context, XMLGregorianCalendar now, + @SuppressWarnings("unused") Task task, @SuppressWarnings("unused") OperationResult result) throws SchemaException { + processActivationBasic(context, now); + processAssignmentActivation(context); } - void processCredentials(LensContext context, XMLGregorianCalendar now, - Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - - if (isFocus(context)) { - //noinspection unchecked - credentialsProcessor.processFocusCredentials((LensContext) context, now, task, result); - } else { - LOGGER.trace("Skipping credentials processing. Not a focus."); - } - } - private void processActivationBasic(LensContext context, XMLGregorianCalendar now, - OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { + private void processActivationBasic(LensContext context, XMLGregorianCalendar now) + throws SchemaException { LensFocusContext focusContext = context.getFocusContext(); - - if (focusContext.isDelete()) { - LOGGER.trace("Skipping processing of focus activation: focus delete"); - return; - } - - processActivationAdministrativeAndValidity(focusContext, now, result); - - if (focusContext.represents(UserType.class)) { - //noinspection unchecked - processActivationLockout((LensFocusContext) focusContext, now, result); - } + processActivationAdministrativeAndValidity(focusContext, now); + processActivationLockout(focusContext); } - private void processAssignmentActivation(LensContext context, XMLGregorianCalendar now, - OperationResult result) throws SchemaException { + private void processAssignmentActivation(LensContext context) throws SchemaException { DeltaSetTriple> evaluatedAssignmentTriple = context.getEvaluatedAssignmentTriple(); if (evaluatedAssignmentTriple == null) { // Code path that should not normally happen. But is used in some tests and may @@ -161,9 +133,7 @@ private void processAssignmentActivation(LensCo } } - private void processActivationAdministrativeAndValidity(LensFocusContext focusContext, XMLGregorianCalendar now, - OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { + private void processActivationAdministrativeAndValidity(LensFocusContext focusContext, XMLGregorianCalendar now) throws SchemaException { TimeIntervalStatusType validityStatusNew = null; TimeIntervalStatusType validityStatusCurrent = null; @@ -224,7 +194,7 @@ private void processActivationAdministrativeAndValidity(Le // some of the mappings will use effectiveStatus as a source, therefore there has to be a delta for the mapping to work correctly recordEffectiveStatusDelta(focusContext, effectiveStatusNew, now); } else { - //check computed effective status current with the saved one - e.g. there can be some inconsistencies so we need to check and force the change.. in other cases, effectvie status will be stored with + //check computed effective status current with the saved one - e.g. there can be some inconsistencies so we need to check and force the change.. in other cases, effective status will be stored with // incorrect value. Maybe another option is to not compute effectiveStatusCurrent if there is an existing (saved) effective status in the user.. TODO if (activationCurrent != null && activationCurrent.getEffectiveStatus() != null) { ActivationStatusType effectiveStatusSaved = activationCurrent.getEffectiveStatus(); @@ -239,15 +209,10 @@ private void processActivationAdministrativeAndValidity(Le LOGGER.trace("Effective status change {} -> {}", effectiveStatusCurrent, effectiveStatusNew); recordEffectiveStatusDelta(focusContext, effectiveStatusNew, now); } - - } - private void processActivationLockout(LensFocusContext focusContext, XMLGregorianCalendar now, - OperationResult result) - throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException { - - ObjectDelta focusPrimaryDelta = focusContext.getPrimaryDelta(); + private void processActivationLockout(LensFocusContext focusContext) throws SchemaException { + ObjectDelta focusPrimaryDelta = focusContext.getPrimaryDelta(); if (focusPrimaryDelta != null) { PropertyDelta lockoutStatusDelta = focusContext.getPrimaryDelta().findPropertyDelta(SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS); if (lockoutStatusDelta != null) { @@ -268,12 +233,12 @@ private void processActivationLockout(LensFocusContext focusNew = focusContext.getObjectNew(); + PrismObject focusNew = focusContext.getObjectNew(); if (focusNew != null) { activationNew = focusNew.asObjectable().getActivation(); if (activationNew != null) { @@ -281,7 +246,7 @@ private void processActivationLockout(LensFocusContext focusCurrent = focusContext.getObjectCurrent(); + PrismObject focusCurrent = focusContext.getObjectCurrent(); if (focusCurrent != null) { activationCurrent = focusCurrent.asObjectable().getActivation(); if (activationCurrent != null) { @@ -306,7 +271,7 @@ private void processActivationLockout(LensFocusContext activationDefinition = getActivationDefinition(); PrismPropertyDefinition lockoutExpirationTimestampDef = activationDefinition.findPropertyDefinition(ActivationType.F_LOCKOUT_EXPIRATION_TIMESTAMP); PropertyDelta lockoutExpirationTimestampDelta @@ -315,10 +280,10 @@ private void processActivationLockout(LensFocusContext focusContext, AbstractCredentialType credentialTypeNew, ItemPath path) throws SchemaException{ + private void resetFailedLogins(LensFocusContext focusContext, AbstractCredentialType credentialTypeNew, ItemPath path) + throws SchemaException { if (credentialTypeNew != null) { Integer failedLogins = credentialTypeNew.getFailedLogins(); if (failedLogins != null && failedLogins != 0) { @@ -406,6 +371,4 @@ private PrismPropertyDefinition getFailedLoginsDefinition() { } return failedLoginsDefinition; } - - } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusLifecycleProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusLifecycleProcessor.java index b1dff7232c4..6bc56e5a400 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusLifecycleProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/FocusLifecycleProcessor.java @@ -1,193 +1,177 @@ -/* - * Copyright (c) 2010-2019 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.focus; - -import java.util.*; - -import javax.xml.datatype.XMLGregorianCalendar; - -import com.evolveum.midpoint.prism.delta.*; -import com.evolveum.midpoint.prism.path.ItemPath; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import com.evolveum.midpoint.repo.common.expression.Expression; -import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; -import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; -import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; -import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; -import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; -import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; -import com.evolveum.midpoint.model.impl.lens.LensContext; -import com.evolveum.midpoint.model.impl.lens.LensFocusContext; -import com.evolveum.midpoint.prism.ItemDefinition; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.PrismObjectDefinition; -import com.evolveum.midpoint.prism.PrismPropertyDefinition; -import com.evolveum.midpoint.prism.PrismPropertyValue; -import com.evolveum.midpoint.schema.constants.ExpressionConstants; -import com.evolveum.midpoint.schema.constants.SchemaConstants; -import com.evolveum.midpoint.schema.result.OperationResult; -import com.evolveum.midpoint.schema.util.LifecycleUtil; -import com.evolveum.midpoint.schema.util.MiscSchemaUtil; -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.ObjectNotFoundException; -import com.evolveum.midpoint.util.exception.PolicyViolationException; -import com.evolveum.midpoint.util.exception.SchemaException; -import com.evolveum.midpoint.util.exception.SecurityViolationException; -import com.evolveum.midpoint.util.logging.Trace; -import com.evolveum.midpoint.util.logging.TraceManager; - -/** - * @author Radovan Semancik - */ -@Component -public class FocusLifecycleProcessor { - - @Autowired private ExpressionFactory expressionFactory; - - private static final Trace LOGGER = TraceManager.getTrace(FocusLifecycleProcessor.class); - - public void processLifecycle(LensContext context, XMLGregorianCalendar now, - Task task, OperationResult result) throws SchemaException, - ObjectNotFoundException, ExpressionEvaluationException, PolicyViolationException, CommunicationException, ConfigurationException, SecurityViolationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - return; - } - if (!AssignmentHolderType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - // We can do this only for FocusType. - return; - } - - //noinspection unchecked - processLifecycleWithFocus((LensContext)context, now, task, result); - } - - private void processLifecycleWithFocus(LensContext context, XMLGregorianCalendar now, - Task task, OperationResult result) throws SchemaException, - ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, ConfigurationException, SecurityViolationException { - - LensFocusContext focusContext = context.getFocusContext(); - ObjectDelta focusDelta = focusContext.getDelta(); - - if (focusDelta != null && focusDelta.isDelete()) { - LOGGER.trace("Skipping lifecycle processing because of focus delete"); - return; - } - - LifecycleStateModelType lifecycleStateModel = focusContext.getLifecycleModel(); - if (lifecycleStateModel == null) { - LOGGER.trace("Skipping lifecycle processing because there is no lifecycle state model for focus"); - return; - } - - PrismObject objectNew = focusContext.getObjectNew(); - String startLifecycleState = objectNew.asObjectable().getLifecycleState(); - if (startLifecycleState == null) { - startLifecycleState = SchemaConstants.LIFECYCLE_ACTIVE; - } - - LifecycleStateType startStateType = LifecycleUtil.findStateDefinition(lifecycleStateModel, startLifecycleState); - if (startStateType == null) { - LOGGER.trace("Skipping lifecycle processing because there is no specification for lifecycle state {}", startLifecycleState); - return; - } - - for (LifecycleStateTransitionType transitionType : startStateType.getTransition()) { - String targetLifecycleState = transitionType.getTargetState(); - if (shouldTransition(context, transitionType, targetLifecycleState, task, result)) { - executeExitActions(context, lifecycleStateModel, startLifecycleState, now, task, result); - LOGGER.debug("Lifecycle state transition of {}: {} -> {}", objectNew, startLifecycleState, targetLifecycleState); - recordLifecycleTransitionDelta(focusContext, targetLifecycleState); - executeEntryActions(context, lifecycleStateModel, targetLifecycleState, now, task, result); - LOGGER.trace("Lifecycle state transition of {} from {} to {} done", objectNew, startLifecycleState, targetLifecycleState); - break; - } - } - } - - private boolean shouldTransition(LensContext context, LifecycleStateTransitionType transitionType, String targetLifecycleState, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { - ExpressionType conditionExpressionType = transitionType.getCondition(); - if (conditionExpressionType == null) { - return false; - } - String desc = "condition for transition to state "+targetLifecycleState+" for "+context.getFocusContext().getHumanReadableName(); - - ExpressionVariables variables = new ExpressionVariables(); - variables.put(ExpressionConstants.VAR_OBJECT, context.getFocusContext().getObjectNew(), context.getFocusContext().getObjectNew().getDefinition()); - // TODO: more variables? - - Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression( - conditionExpressionType, ExpressionUtil.createConditionOutputDefinition(context.getPrismContext()), - MiscSchemaUtil.getExpressionProfile(), desc, task, result); - ExpressionEvaluationContext expressionContext = new ExpressionEvaluationContext(null , variables, desc, task); - ExpressionEnvironment env = new ExpressionEnvironment<>(context, null, task, result); - PrismValueDeltaSetTriple> outputTriple = - ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, expressionContext, env, result); - PrismPropertyValue expressionOutputValue = ExpressionUtil.getExpressionOutputValue(outputTriple, desc); - return ExpressionUtil.getBooleanConditionOutput(expressionOutputValue); - } - - private void recordLifecycleTransitionDelta(LensFocusContext focusContext, String targetLifecycleState) throws SchemaException { - PropertyDelta lifecycleDelta = focusContext.getPrismContext().deltaFactory().property() - .createModificationReplaceProperty(ObjectType.F_LIFECYCLE_STATE, focusContext.getObjectDefinition(), - targetLifecycleState); - focusContext.swallowToSecondaryDelta(lifecycleDelta); - } - - private void executeEntryActions(LensContext context, LifecycleStateModelType lifecycleStateModel, - String targetLifecycleState, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { - LifecycleStateType stateType = LifecycleUtil.findStateDefinition(lifecycleStateModel, targetLifecycleState); - if (stateType == null) { - return; - } - executeStateActions(context, targetLifecycleState, stateType.getEntryAction(), "entry", now, task, result); - } - - private void executeExitActions(LensContext context, LifecycleStateModelType lifecycleStateModel, - String targetLifecycleState, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { - LifecycleStateType stateType = LifecycleUtil.findStateDefinition(lifecycleStateModel, targetLifecycleState); - if (stateType == null) { - return; - } - executeStateActions(context, targetLifecycleState, stateType.getExitAction(), "exit", now, task, result); - } - - private void executeStateActions(LensContext context, String targetLifecycleState, - List actions, String actionTypeDesc, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { - for (LifecycleStateActionType action: actions) { - LOGGER.trace("Execute {} action {} for state {} of {}", actionTypeDesc, action.getName(), targetLifecycleState, context.getFocusContext().getObjectNew()); - executeDataReduction(context, action.getDataReduction(), now, task, result); - } - } - - private void executeDataReduction(LensContext context, LifecycleStateActionDataReductionType dataReduction, - XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { - if (dataReduction == null) { - return; - } - LensFocusContext focusContext = context.getFocusContext(); - PrismObjectDefinition focusDefinition = focusContext.getObjectDefinition(); - for (ItemPathType purgeItemPathType : dataReduction.getPurgeItem()) { - ItemPath purgeItemPath = purgeItemPathType.getItemPath(); - LOGGER.trace("Purging item {} from {}", purgeItemPath, focusContext.getObjectNew()); - ItemDefinition purgeItemDef = focusDefinition.findItemDefinition(purgeItemPath); - ItemDelta purgeItemDelta = purgeItemDef.createEmptyDelta(purgeItemPath); - purgeItemDelta.setValueToReplace(); - focusContext.swallowToSecondaryDelta(purgeItemDelta); - } - } - -} +/* + * Copyright (c) 2010-2019 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.focus; + +import java.util.*; + +import javax.xml.datatype.XMLGregorianCalendar; + +import com.evolveum.midpoint.model.impl.lens.projector.ProjectorProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; +import com.evolveum.midpoint.prism.delta.*; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.evolveum.midpoint.repo.common.expression.Expression; +import com.evolveum.midpoint.repo.common.expression.ExpressionEvaluationContext; +import com.evolveum.midpoint.repo.common.expression.ExpressionFactory; +import com.evolveum.midpoint.repo.common.expression.ExpressionUtil; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.model.common.expression.ExpressionEnvironment; +import com.evolveum.midpoint.model.common.expression.ModelExpressionThreadLocalHolder; +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.model.impl.lens.LensFocusContext; +import com.evolveum.midpoint.prism.ItemDefinition; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismObjectDefinition; +import com.evolveum.midpoint.prism.PrismPropertyDefinition; +import com.evolveum.midpoint.prism.PrismPropertyValue; +import com.evolveum.midpoint.schema.constants.ExpressionConstants; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.LifecycleUtil; +import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +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.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.util.exception.SecurityViolationException; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; + +import static com.evolveum.midpoint.model.impl.lens.projector.util.SkipWhenFocusDeleted.PRIMARY_OR_SECONDARY; + +/** + * @author Radovan Semancik + */ +@Component +@ProcessorExecution(focusRequired = true, focusType = AssignmentHolderType.class, skipWhenFocusDeleted = PRIMARY_OR_SECONDARY) +public class FocusLifecycleProcessor implements ProjectorProcessor { + + @Autowired private ExpressionFactory expressionFactory; + + private static final Trace LOGGER = TraceManager.getTrace(FocusLifecycleProcessor.class); + + @ProcessorMethod + public void process(LensContext context, XMLGregorianCalendar now, + Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ConfigurationException, + CommunicationException, SecurityViolationException { + + LensFocusContext focusContext = context.getFocusContext(); + + LifecycleStateModelType lifecycleStateModel = focusContext.getLifecycleModel(); + if (lifecycleStateModel == null) { + LOGGER.trace("Skipping lifecycle processing because there is no lifecycle state model for focus"); + return; + } + + PrismObject objectNew = focusContext.getObjectNew(); + String startLifecycleState = objectNew.asObjectable().getLifecycleState(); + if (startLifecycleState == null) { + startLifecycleState = SchemaConstants.LIFECYCLE_ACTIVE; + } + + LifecycleStateType startStateType = LifecycleUtil.findStateDefinition(lifecycleStateModel, startLifecycleState); + if (startStateType == null) { + LOGGER.trace("Skipping lifecycle processing because there is no specification for lifecycle state {}", startLifecycleState); + return; + } + + for (LifecycleStateTransitionType transitionType : startStateType.getTransition()) { + String targetLifecycleState = transitionType.getTargetState(); + if (shouldTransition(context, transitionType, targetLifecycleState, task, result)) { + executeExitActions(context, lifecycleStateModel, startLifecycleState, now, task, result); + LOGGER.debug("Lifecycle state transition of {}: {} -> {}", objectNew, startLifecycleState, targetLifecycleState); + recordLifecycleTransitionDelta(focusContext, targetLifecycleState); + executeEntryActions(context, lifecycleStateModel, targetLifecycleState, now, task, result); + LOGGER.trace("Lifecycle state transition of {} from {} to {} done", objectNew, startLifecycleState, targetLifecycleState); + break; + } + } + } + + private boolean shouldTransition(LensContext context, LifecycleStateTransitionType transitionType, String targetLifecycleState, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, CommunicationException, ConfigurationException, SecurityViolationException { + ExpressionType conditionExpressionType = transitionType.getCondition(); + if (conditionExpressionType == null) { + return false; + } + String desc = "condition for transition to state "+targetLifecycleState+" for "+context.getFocusContext().getHumanReadableName(); + + ExpressionVariables variables = new ExpressionVariables(); + variables.put(ExpressionConstants.VAR_OBJECT, context.getFocusContext().getObjectNew(), context.getFocusContext().getObjectNew().getDefinition()); + // TODO: more variables? + + Expression,PrismPropertyDefinition> expression = expressionFactory.makeExpression( + conditionExpressionType, ExpressionUtil.createConditionOutputDefinition(context.getPrismContext()), + MiscSchemaUtil.getExpressionProfile(), desc, task, result); + ExpressionEvaluationContext expressionContext = new ExpressionEvaluationContext(null , variables, desc, task); + ExpressionEnvironment env = new ExpressionEnvironment<>(context, null, task, result); + PrismValueDeltaSetTriple> outputTriple = + ModelExpressionThreadLocalHolder.evaluateExpressionInContext(expression, expressionContext, env, result); + PrismPropertyValue expressionOutputValue = ExpressionUtil.getExpressionOutputValue(outputTriple, desc); + return ExpressionUtil.getBooleanConditionOutput(expressionOutputValue); + } + + private void recordLifecycleTransitionDelta(LensFocusContext focusContext, String targetLifecycleState) throws SchemaException { + PropertyDelta lifecycleDelta = focusContext.getPrismContext().deltaFactory().property() + .createModificationReplaceProperty(ObjectType.F_LIFECYCLE_STATE, focusContext.getObjectDefinition(), + targetLifecycleState); + focusContext.swallowToSecondaryDelta(lifecycleDelta); + } + + private void executeEntryActions(LensContext context, LifecycleStateModelType lifecycleStateModel, + String targetLifecycleState, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { + LifecycleStateType stateType = LifecycleUtil.findStateDefinition(lifecycleStateModel, targetLifecycleState); + if (stateType == null) { + return; + } + executeStateActions(context, targetLifecycleState, stateType.getEntryAction(), "entry", now, task, result); + } + + private void executeExitActions(LensContext context, LifecycleStateModelType lifecycleStateModel, + String targetLifecycleState, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { + LifecycleStateType stateType = LifecycleUtil.findStateDefinition(lifecycleStateModel, targetLifecycleState); + if (stateType == null) { + return; + } + executeStateActions(context, targetLifecycleState, stateType.getExitAction(), "exit", now, task, result); + } + + private void executeStateActions(LensContext context, String targetLifecycleState, + List actions, String actionTypeDesc, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException { + for (LifecycleStateActionType action: actions) { + LOGGER.trace("Execute {} action {} for state {} of {}", actionTypeDesc, action.getName(), targetLifecycleState, context.getFocusContext().getObjectNew()); + executeDataReduction(context, action.getDataReduction()); + } + } + + private void executeDataReduction(LensContext context, LifecycleStateActionDataReductionType dataReduction) + throws SchemaException { + if (dataReduction == null) { + return; + } + LensFocusContext focusContext = context.getFocusContext(); + PrismObjectDefinition focusDefinition = focusContext.getObjectDefinition(); + for (ItemPathType purgeItemPathType : dataReduction.getPurgeItem()) { + ItemPath purgeItemPath = purgeItemPathType.getItemPath(); + LOGGER.trace("Purging item {} from {}", purgeItemPath, focusContext.getObjectNew()); + ItemDefinition purgeItemDef = focusDefinition.findItemDefinition(purgeItemPath); + ItemDelta purgeItemDelta = purgeItemDef.createEmptyDelta(purgeItemPath); + purgeItemDelta.setValueToReplace(); + focusContext.swallowToSecondaryDelta(purgeItemDelta); + } + } +} 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 e723fae42f0..8b98cca4f3f 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 @@ -20,6 +20,9 @@ import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; +import com.evolveum.midpoint.model.impl.lens.projector.ProjectorProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.prism.equivalence.EquivalenceStrategy; @@ -72,6 +75,8 @@ import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; +import static com.evolveum.midpoint.model.impl.lens.projector.util.SkipWhenFocusDeleted.PRIMARY_OR_SECONDARY; + /** * Processor that takes changes from accounts and synchronization deltas and updates user attributes if necessary * (by creating secondary user object delta {@link ObjectDelta}). @@ -80,7 +85,8 @@ * @author Radovan Semancik */ @Component -public class InboundProcessor { +@ProcessorExecution(focusRequired = true, focusType = FocusType.class, skipWhenFocusDeleted = PRIMARY_OR_SECONDARY) +public class InboundProcessor implements ProjectorProcessor { private static final Trace LOGGER = TraceManager.getTrace(InboundProcessor.class); @@ -90,47 +96,15 @@ public class InboundProcessor { @Autowired private CredentialsProcessor credentialsProcessor; @Autowired private MappingEvaluator mappingEvaluator; @Autowired private Protector protector; + @Autowired private ClockworkMedic medic; @Autowired private ProvisioningService provisioningService; private PrismContainerDefinition associationContainerDefinition; -// private Map>> mappingsToTarget; - - void processInbound(LensContext context, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ConfigurationException, CommunicationException, SecurityViolationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - LOGGER.trace("Skipping inbound because there is no focus"); - return; - } - if (!FocusType.class.isAssignableFrom(focusContext.getObjectTypeClass())) { - // We can do this only for focus types. - LOGGER.trace("Skipping inbound because {} is not focal type", focusContext.getObjectTypeClass()); - return; - } - //noinspection unchecked - processInboundFocal((LensContext)context, task, now, result); - } - - private void processInboundFocal(LensContext context, Task task, XMLGregorianCalendar now, - OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ConfigurationException, CommunicationException, SecurityViolationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - LOGGER.trace("Skipping inbound processing because focus is null"); - return; - } - if (focusContext.isDelete()) { - LOGGER.trace("Skipping inbound processing because focus is being deleted"); - return; - } - - ObjectDelta userSecondaryDelta = focusContext.getProjectionWaveSecondaryDelta(); - - if (userSecondaryDelta != null && ChangeType.DELETE.equals(userSecondaryDelta.getChangeType())) { - //we don't need to do inbound if we are deleting this user - LOGGER.trace("Skipping inbound processing because focus is being deleted (secondary delta)"); - return; - } - + @ProcessorMethod + void processInbounds(LensContext context, String activityDescription, XMLGregorianCalendar now, Task task, OperationResult result) + throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException, ConfigurationException, + CommunicationException, SecurityViolationException { // Used to collect all the mappings from all the projects, sorted by target property. // Motivation: we need to evaluate them together, e.g. in case that there are several mappings // from several projections targeting the same property. @@ -174,6 +148,17 @@ private void processInboundFocal(LensContext context, T } evaluateInboundMapping(mappingsToTarget, context, task, result); + + context.checkConsistenceIfNeeded(); + context.recomputeFocus(); + medic.traceContext(LOGGER, activityDescription, "inbound", false, context, false); + + // It's actually a bit questionable if such cross-components interactions should be treated like this + // or in some higher-level component. But let's try this approach until something nicer is found. + contextLoader.updateArchetypePolicy(context, task, result); + contextLoader.updateArchetype(context, task, result); + contextLoader.updateFocusTemplate(context, result); + context.checkConsistenceIfNeeded(); } private boolean isDeleteAccountDelta(LensProjectionContext accountContext) throws SchemaException { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/ItemLimitationsChecker.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/ItemLimitationsChecker.java new file mode 100644 index 00000000000..a296fd60b85 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/ItemLimitationsChecker.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2020 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.focus; + +import com.evolveum.midpoint.model.impl.lens.LensFocusContext; +import com.evolveum.midpoint.prism.Item; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.path.UniformItemPath; +import com.evolveum.midpoint.prism.util.DefinitionUtil; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.LayerType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTemplateItemDefinitionType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.PropertyLimitationsType; + +import org.springframework.stereotype.Component; + +import java.util.Map; + +import static org.apache.commons.lang3.BooleanUtils.isTrue; + +/** + * Checks item limitations. + * + * This is a preliminary implementation; to be merged with something related. + */ +@Component +public class ItemLimitationsChecker { + + /** + * @pre Focus context is recomputed. + */ + void checkItemsLimitations(LensFocusContext focusContext) throws SchemaException { + Map itemDefinitionsMap = focusContext.getItemDefinitionsMap(); + PrismObject objectNew = focusContext.getObjectNew(); + if (objectNew == null) { + return; // nothing to check on DELETE operation + } + for (Map.Entry entry : itemDefinitionsMap.entrySet()) { + for (PropertyLimitationsType limitation : entry.getValue().getLimitations()) { + if (!limitation.getLayer().contains(LayerType.MODEL)) { // or should we apply SCHEMA-layer limitations as well? + continue; + } + checkItemLimitations(objectNew, entry.getKey(), limitation); + } + } + } + + private void checkItemLimitations(PrismObject object, ItemPath path, PropertyLimitationsType limitation) + throws SchemaException { + Object item = object.find(path); + if (isTrue(limitation.isIgnore())) { + return; + } + int count = getValueCount(item); + Integer min = DefinitionUtil.parseMultiplicity(limitation.getMinOccurs()); + if (min != null && min > 0 && count < min) { + throw new SchemaException("Expected at least " + min + " values of " + path + ", got " + count); + } + Integer max = DefinitionUtil.parseMultiplicity(limitation.getMaxOccurs()); + if (max != null && max >= 0 && count > max) { + throw new SchemaException("Expected at most " + max + " values of " + path + ", got " + count); + } + } + + private int getValueCount(Object item) { + if (item == null) { + return 0; + } + if (!(item instanceof Item)) { + throw new IllegalStateException("Expected Item but got " + item.getClass() + " instead"); + } + return ((Item) item).getValues().size(); + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/IterationHelper.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/IterationHelper.java new file mode 100644 index 00000000000..00ce12e9b0d --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/IterationHelper.java @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2020 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.focus; + +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.model.impl.lens.LensFocusContext; +import com.evolveum.midpoint.model.impl.lens.LensUtil; +import com.evolveum.midpoint.model.impl.util.ModelImplUtils; +import com.evolveum.midpoint.prism.*; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.prism.delta.PropertyDelta; +import com.evolveum.midpoint.repo.common.expression.ExpressionVariables; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.util.logging.Trace; +import com.evolveum.midpoint.util.logging.TraceManager; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; + +import org.jetbrains.annotations.NotNull; + +/** + * Helps AssignmentHolderProcessor with iteration-related activities. + * 1. keeps iteration state + * 2. evaluates pre/post and uniqueness conditions (including name presence) + * 3. manages fetching and storing of iteration information from/to lens context + * + * And nothing more. + */ +class IterationHelper { + + private static final Trace LOGGER = TraceManager.getTrace(IterationHelper.class); + + @NotNull private final AssignmentHolderProcessor assignmentHolderProcessor; + @NotNull private final LensContext context; + @NotNull private final LensFocusContext focusContext; + + /** + * Current iteration specification. Might change during the processing! + */ + private IterationSpecificationType iterationSpecification; + + /** + * We initialize iteration specification lazily. So this is the flag. + */ + private boolean iterationSpecificationInitialized; + + /** + * Currently we allow only a single reset when iteration specification changes + * (to avoid endless loops, perhaps). + */ + private boolean wasResetOnIterationSpecificationChange; + + /** + * Current iteration number. + */ + private int iteration; + + /** + * Current iteration token. + */ + private String iterationToken; + + /** + * We allow only single reset of iteration counter (except when iteration specification + * changes). So this is the flag. + */ + private boolean wasResetIterationCounter; + + /** + * Upper iterations limit. + */ + private int maxIterations; + + /** + * Lazily evaluated pre-iteration variables. + */ + private ExpressionVariables variablesPreIteration; + + /** + * Message about re-iteration reason. + */ + private String reIterationReason; + + private static final boolean RESET_ON_RENAME = true; // make configurable some day + + IterationHelper(@NotNull AssignmentHolderProcessor assignmentHolderProcessor, @NotNull LensContext context, + @NotNull LensFocusContext focusContext) { + this.assignmentHolderProcessor = assignmentHolderProcessor; + this.context = context; + this.focusContext = focusContext; + iteration = focusContext.getIteration(); + iterationToken = focusContext.getIterationToken(); + PrismObject focusCurrent = focusContext.getObjectCurrent(); + if (focusCurrent != null && iterationToken == null) { + Integer focusIteration = focusCurrent.asObjectable().getIteration(); + if (focusIteration != null) { + iteration = focusIteration; + } + iterationToken = focusCurrent.asObjectable().getIterationToken(); + } + } + + void onIterationStart(Task task, OperationResult result) throws CommunicationException, ObjectNotFoundException, + SchemaException, SecurityViolationException, ConfigurationException, ExpressionEvaluationException { + reIterationReason = null; + initializeIterationSpecificationIfNeeded(); + variablesPreIteration = null; + computeIterationTokenIfNeeded(task, result); + rememberIterationToken(); + LOGGER.trace("Focus {} processing, iteration {}, token '{}'", focusContext.getHumanReadableName(), iteration, iterationToken); + } + + private void computeIterationTokenIfNeeded(Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException, CommunicationException, + ConfigurationException, SecurityViolationException { + if (iterationToken == null) { + createVariablesPreIterationIfNeeded(); + iterationToken = LensUtil.formatIterationToken(context, focusContext, + iterationSpecification, iteration, assignmentHolderProcessor.getExpressionFactory(), variablesPreIteration, task, result); + } + } + + private void rememberIterationToken() { + // We have to remember the token and iteration in the context. + // The context can be recomputed several times. But we always want + // to use the same iterationToken if possible. If there is a random + // part in the iterationToken expression that we need to avoid recomputing + // the token otherwise the value can change all the time (even for the same inputs). + // Storing the token in the secondary delta is not enough because secondary deltas can be dropped + // if the context is re-projected. + focusContext.setIteration(iteration); + focusContext.setIterationToken(iterationToken); + } + + private void initializeIterationSpecificationIfNeeded() { + if (!iterationSpecificationInitialized) { + ObjectTemplateType objectTemplate = context.getFocusTemplate(); + iterationSpecification = LensUtil.getIterationSpecification(objectTemplate); + maxIterations = LensUtil.determineMaxIterations(iterationSpecification); + LOGGER.trace("maxIterations = {}, iteration specification = {} derived from template {}", maxIterations, + iterationSpecification, objectTemplate); + iterationSpecificationInitialized = true; + } + } + + private void createVariablesPreIterationIfNeeded() { + if (variablesPreIteration == null) { + variablesPreIteration = ModelImplUtils.getDefaultExpressionVariables(focusContext.getObjectNew(), + null, null, null, context.getSystemConfiguration(), focusContext, assignmentHolderProcessor.getPrismContext()); + } + } + + boolean doesPreIterationConditionHold(Task task, OperationResult result) throws CommunicationException, + ObjectNotFoundException, SchemaException, SecurityViolationException, ConfigurationException, ExpressionEvaluationException { + if (iterationSpecification != null) { + createVariablesPreIterationIfNeeded(); + if (!LensUtil.evaluateIterationCondition(context, focusContext, iterationSpecification, iteration, + iterationToken, true, assignmentHolderProcessor.getExpressionFactory(), variablesPreIteration, task, result)) { + reIterationReason = "pre-iteration condition was false"; + LOGGER.debug("Skipping iteration {}, token '{}' for {} because the pre-iteration condition was false", + iteration, iterationToken, focusContext.getHumanReadableName()); + return false; + } + } + return true; + } + + private void resetOnIterationSpecificationChange() { + iteration = 0; + iterationToken = null; + wasResetOnIterationSpecificationChange = true; + wasResetIterationCounter = false; + iterationSpecificationInitialized = false; + LOGGER.trace("Resetting iteration counter and token because of iteration specification change"); + } + + private void resetOnRename() { + iteration = 0; + iterationToken = null; + wasResetIterationCounter = true; + LOGGER.trace("Resetting iteration counter and token because rename was detected"); + } + + private void resetOnConflict() { + iteration = 0; + iterationToken = null; + wasResetIterationCounter = true; + LOGGER.trace("Resetting iteration counter and token after conflict"); + } + + boolean didIterationSpecificationChange() { + if (wasResetOnIterationSpecificationChange) { + return false; // We won't reset on spec change twice. + } + IterationSpecificationType newIterationSpecification = context.getFocusTemplate() != null ? + context.getFocusTemplate().getIterationSpecification() : null; + if (java.util.Objects.equals(iterationSpecification, newIterationSpecification)) { + return false; + } else { + resetOnIterationSpecificationChange(); + return true; + } + } + + boolean isIterationOk(PrismObject objectNew, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + checkNamePresence(context, this, objectNew); + FocusConstraintsChecker checker = createChecker(context); + if (!shouldCheckConstraints() || checker.check(objectNew, result)) { + LOGGER.trace("Current focus satisfies uniqueness constraints. Iteration {}, token '{}'", iteration, iterationToken); + ExpressionVariables variablesPostIteration = ModelImplUtils.getDefaultExpressionVariables(objectNew, + null, null, null, context.getSystemConfiguration(), focusContext, assignmentHolderProcessor.getPrismContext()); + if (LensUtil.evaluateIterationCondition(context, focusContext, + iterationSpecification, iteration, iterationToken, false, assignmentHolderProcessor.getExpressionFactory(), variablesPostIteration, + task, result)) { + return true; + } else { + reIterationReason = "post-iteration condition was false"; + LOGGER.debug("Skipping iteration {}, token '{}' for {} because the post-iteration condition was false", + iteration, iterationToken, focusContext.getHumanReadableName()); + return false; + } + } else { + LOGGER.trace("Current focus does not satisfy constraints. Conflicting object: {}; iteration={}, maxIterations={}", + checker.getConflictingObject(), iteration, maxIterations); + reIterationReason = checker.getMessages(); + return false; + } + } + + @NotNull + private FocusConstraintsChecker createChecker(LensContext context) { + FocusConstraintsChecker checker = new FocusConstraintsChecker<>(); + checker.setPrismContext(assignmentHolderProcessor.getPrismContext()); + checker.setContext(context); + checker.setRepositoryService(assignmentHolderProcessor.getCacheRepositoryService()); + checker.setCacheConfigurationManager(assignmentHolderProcessor.getCacheConfigurationManager()); + return checker; + } + + private boolean shouldCheckConstraints() throws SchemaException { + ConstraintsCheckingStrategyType strategy = context.getFocusConstraintsCheckingStrategy(); + boolean skipWhenNoChange = strategy != null && Boolean.TRUE.equals(strategy.isSkipWhenNoChange()); + boolean skipWhenNoIteration = strategy != null && Boolean.TRUE.equals(strategy.isSkipWhenNoIteration()); + + if (skipWhenNoChange && !hasNameDelta()) { + LOGGER.trace("Skipping constraints check because 'skipWhenNoChange' is true and there's no name delta"); + return false; + } else if (skipWhenNoIteration && maxIterations == 0) { + LOGGER.trace("Skipping constraints check because 'skipWhenNoIteration' is true and there is no iteration defined"); + return false; + } else if (TaskType.class == focusContext.getObjectTypeClass()) { + LOGGER.trace("Skipping constraints check for task, not needed because tasks names are not unique."); + return false; + } else { + return true; + } + } + + void incrementIterationCounter() throws ObjectAlreadyExistsException { + iteration++; + iterationToken = null; + LensUtil.checkMaxIterations(iteration, maxIterations, reIterationReason, focusContext.getHumanReadableName()); + } + + boolean didResetOnRenameOccur() throws SchemaException { + if (iteration != 0 && RESET_ON_RENAME && !wasResetIterationCounter && willResetIterationCounter()) { + // Make sure this happens only the very first time during the first recompute. + // Otherwise it will always change the token (especially if the token expression has a random part) + // hence the focusContext.getIterationToken() == null + resetOnRename(); + return true; + } else { + return false; + } + } + + private boolean willResetIterationCounter() throws SchemaException { + ObjectDelta focusDelta = focusContext.getDelta(); + if (focusDelta == null) { + return false; + } + if (focusContext.isAdd() || focusContext.isDelete()) { + return false; + } + if (focusDelta.findPropertyDelta(FocusType.F_ITERATION) != null) { + // there was a reset already in previous projector runs + return false; + } + // Check for rename + return hasNameDelta(focusDelta); + } + + private boolean hasNameDelta() throws SchemaException { + ObjectDelta focusDelta = focusContext.getDelta(); + return focusDelta != null && hasNameDelta(focusDelta); + } + + private boolean hasNameDelta(ObjectDelta focusDelta) { + PropertyDelta nameDelta = focusDelta.findPropertyDelta(FocusType.F_NAME); + return nameDelta != null; + } + + boolean shouldResetOnConflict() { + if (!wasResetIterationCounter && iteration != 0) { + resetOnConflict(); + return true; + } else { + return false; + } + } + + private void checkNamePresence(LensContext context, + IterationHelper ctx, @NotNull PrismObject objectNew) throws NoFocusNameSchemaException { + // Explicitly check for name. The checker would check for this also. But checking it here + // will produce better error message + PolyStringType objectName = objectNew.asObjectable().getName(); + if (objectName == null || objectName.getOrig().isEmpty()) { + throw new NoFocusNameSchemaException("No name in new object " + objectName + " as produced by template " + context.getFocusTemplate() + + " in iteration " + ctx.iteration + ", we cannot process an object without a name"); + } + } + + /** + * Adds deltas for iteration and iterationToken to the focus if needed. + */ + void createIterationTokenDeltas() throws SchemaException { + PrismContext prismContext = assignmentHolderProcessor.getPrismContext(); + + PrismObject objectCurrent = focusContext.getObjectCurrent(); + if (objectCurrent != null) { + Integer iterationOld = objectCurrent.asObjectable().getIteration(); + String iterationTokenOld = objectCurrent.asObjectable().getIterationToken(); + if (iterationOld != null && iterationOld == iteration && + iterationTokenOld != null && iterationTokenOld.equals(iterationToken)) { + // Already stored + return; + } + } + PrismObjectDefinition objDef = focusContext.getObjectDefinition(); + + PrismPropertyValue iterationVal = prismContext.itemFactory().createPropertyValue(iteration); + iterationVal.setOriginType(OriginType.USER_POLICY); + PropertyDelta iterationDelta = prismContext.deltaFactory().property().createReplaceDelta(objDef, + FocusType.F_ITERATION, iterationVal); + focusContext.swallowToSecondaryDelta(iterationDelta); + + PrismPropertyValue iterationTokenVal = prismContext.itemFactory().createPropertyValue(iterationToken); + iterationTokenVal.setOriginType(OriginType.USER_POLICY); + PropertyDelta iterationTokenDelta = prismContext.deltaFactory().property().createReplaceDelta(objDef, + FocusType.F_ITERATION_TOKEN, iterationTokenVal); + focusContext.swallowToSecondaryDelta(iterationTokenDelta); + } +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/ObjectTemplateProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/ObjectTemplateProcessor.java index 96ac81b126f..316ccc949c6 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/ObjectTemplateProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/ObjectTemplateProcessor.java @@ -17,7 +17,10 @@ import javax.xml.namespace.QName; import com.evolveum.midpoint.model.impl.lens.LensUtil; +import com.evolveum.midpoint.model.impl.lens.projector.ProjectorProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; import com.evolveum.midpoint.model.impl.lens.projector.mappings.*; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.prism.equivalence.EquivalenceStrategy; import com.evolveum.midpoint.prism.path.ItemPathCollectionsUtil; @@ -82,6 +85,8 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.VariableBindingDefinitionType; import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; +import static com.evolveum.midpoint.model.impl.lens.projector.util.SkipWhenFocusDeleted.PRIMARY; +import static com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTemplateMappingEvaluationPhaseType.AFTER_ASSIGNMENTS; import static com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectTemplateMappingEvaluationPhaseType.BEFORE_ASSIGNMENTS; /** @@ -91,7 +96,8 @@ * */ @Component -public class ObjectTemplateProcessor { +@ProcessorExecution(focusRequired = true, focusType = FocusType.class, skipWhenFocusDeleted = PRIMARY) +public class ObjectTemplateProcessor implements ProjectorProcessor { private static final Trace LOGGER = TraceManager.getTrace(ObjectTemplateProcessor.class); @@ -105,18 +111,27 @@ public class ObjectTemplateProcessor { @Autowired private MappingSetEvaluator mappingSetEvaluator; @Autowired private MatchingRuleRegistry matchingRuleRegistry; - /** - * Process focus template: application of object template where focus is both source and target. - */ - void processTemplate(LensContext context, + @ProcessorMethod + void processTemplateBeforeAssignments(LensContext context, + XMLGregorianCalendar now, Task task, OperationResult result) + throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, + SecurityViolationException, ConfigurationException, CommunicationException { + processTemplate(context, BEFORE_ASSIGNMENTS, now, task, result); + } + + @ProcessorMethod + void processTemplateAfterAssignments(LensContext context, + XMLGregorianCalendar now, Task task, OperationResult result) + throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, + SecurityViolationException, ConfigurationException, CommunicationException { + processTemplate(context, AFTER_ASSIGNMENTS, now, task, result); + } + + private void processTemplate(LensContext context, ObjectTemplateMappingEvaluationPhaseType phase, XMLGregorianCalendar now, Task task, OperationResult result) throws ExpressionEvaluationException, ObjectNotFoundException, SchemaException, PolicyViolationException, SecurityViolationException, ConfigurationException, CommunicationException { LensFocusContext focusContext = context.getFocusContext(); - if (focusContext.isDelete()) { - LOGGER.trace("Skipping processing of object template: focus delete"); - return; - } ObjectTemplateType objectTemplate = context.getFocusTemplate(); String objectTemplateDesc; @@ -163,6 +178,8 @@ void processTemplate(LensContext context, if (nextRecompute != null) { nextRecompute.createTrigger(focusContext); } + + focusContext.recompute(); } /** diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleEnforcer.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleEnforcer.java index cddfb944ff5..2dd98135252 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleEnforcer.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleEnforcer.java @@ -42,9 +42,6 @@ public class PolicyRuleEnforcer { //private static final Trace LOGGER = TraceManager.getTrace(PolicyRuleEnforcer.class); - // deprecated - private static final String HOOK_URI = SchemaConstants.NS_MODEL + "/policy-rule-enforcer-hook-3"; - @Autowired private PrismContext prismContext; @Autowired private LocalizationService localizationService; @@ -104,6 +101,7 @@ private void evaluateAssignmentRules(EvaluationContext eva if (evaluatedAssignmentTriple == null) { return; } + //noinspection unchecked evaluatedAssignmentTriple.simpleAccept(assignment -> enforceTriggeredRules(evalCtx, assignment.getAllTargetsPolicyRules())); } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java index 903cbd3d2c0..99436caf9d6 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/policy/PolicyRuleProcessor.java @@ -11,8 +11,11 @@ import com.evolveum.midpoint.model.common.mapping.MappingFactory; import com.evolveum.midpoint.model.impl.lens.*; import com.evolveum.midpoint.model.impl.lens.projector.AssignmentOrigin; +import com.evolveum.midpoint.model.impl.lens.projector.ProjectorProcessor; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorExecution; import com.evolveum.midpoint.model.impl.lens.projector.mappings.MappingEvaluator; import com.evolveum.midpoint.model.impl.lens.projector.policy.evaluators.*; +import com.evolveum.midpoint.model.impl.lens.projector.util.ProcessorMethod; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.*; import com.evolveum.midpoint.prism.util.CloneUtil; @@ -56,7 +59,8 @@ * @author mederly */ @Component -public class PolicyRuleProcessor { +@ProcessorExecution(focusRequired = true, focusType = AssignmentHolderType.class) +public class PolicyRuleProcessor implements ProjectorProcessor { private static final Trace LOGGER = TraceManager.getTrace(PolicyRuleProcessor.class); @@ -233,14 +237,10 @@ private void resolveReferences(Collection evaluatedRules, C // } //region ------------------------------------------------------------------ Focus policy rules - public void evaluateObjectPolicyRules(LensContext context, String activityDescription, + @ProcessorMethod + public void evaluateObjectPolicyRules(LensContext context, XMLGregorianCalendar now, Task task, OperationResult result) throws PolicyViolationException, SchemaException, ExpressionEvaluationException, ObjectNotFoundException, SecurityViolationException, ConfigurationException, CommunicationException { - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext == null) { - return; - } - RulesEvaluationContext globalCtx = new RulesEvaluationContext(); List rules = new ArrayList<>(); @@ -253,6 +253,7 @@ public void evaluateObjectPolicyRules(LensCont List nonSituationRules = new ArrayList<>(); LOGGER.trace("Evaluating {} object policy rules", rules.size()); + LensFocusContext focusContext = context.getFocusContext(); focusContext.clearPolicyRules(); for (EvaluatedPolicyRule rule : rules) { if (isApplicableToObject(rule)) { diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorExecution.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorExecution.java new file mode 100644 index 00000000000..d3bf3c40906 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorExecution.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020 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.util; + +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specifies requirements on execution of processor methods through ClockworkMedic.partialExecute + * and related methods. + *

+ * Beware that these requirements apply to all methods callable via ClockworkMedic ({@link ProcessorMethod}). + *

+ * In the future we might consider declaring execution requirements directly for those methods. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Experimental +public @interface ProcessorExecution { + + /** + * Is the focus context required? (Usually yes, but let's be explicit.) + */ + boolean focusRequired() default false; + + /** + * What kind of focus there should be in order for the processor methods to be executed? + * Checked only if focusRequired = true. + */ + Class focusType() default ObjectType.class; + + /** + * Should the methods execution be skipped if the focus is going to be deleted? + */ + SkipWhenFocusDeleted skipWhenFocusDeleted() default SkipWhenFocusDeleted.NONE; + + /** + * Should the execution be skipped if the projection is to be deleted? + * (We should perhaps make this more flexible in the future.) + */ + boolean skipWhenProjectionDeleted() default false; +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorMethod.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorMethod.java new file mode 100644 index 00000000000..587663ee9a7 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorMethod.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020 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.util; + +import com.evolveum.midpoint.util.annotation.Experimental; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation for medic-invocable projection processor method. + * It is used basically as a reminder that this method is called under checks + * of ClockworkMedic. In the future we might declare execution requirements here. + * + * (Unfortunately, Java does not provide us with the annotation of the method referenced in + * "component::method" way. So this is only a wish for the time being.) + * + * We should consider finding a better name for this annotation. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Experimental +public @interface ProcessorMethod { +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorMethodRef.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorMethodRef.java new file mode 100644 index 00000000000..b8fd0b6eb67 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorMethodRef.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 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.util; + +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.repo.api.PreconditionViolationException; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +import javax.xml.datatype.XMLGregorianCalendar; + +/** + * Typical processor "component-level" method that performs a well defined part of the computation. + * This is the full version for focus-level, i.e. with activityDescription and without projection context. + * + * @param Fake type parameter that is necessary to make type inference in partialProcessorExecute methods happy. + */ +@Experimental +@FunctionalInterface +public interface ProcessorMethodRef { + + void run(LensContext lensContext, String activityDescription, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PreconditionViolationException; + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProjectionAwareProcessorMethodRef.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProjectionAwareProcessorMethodRef.java new file mode 100644 index 00000000000..af7842247b7 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProjectionAwareProcessorMethodRef.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 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.util; + +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.model.impl.lens.LensProjectionContext; +import com.evolveum.midpoint.repo.api.PreconditionViolationException; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +import javax.xml.datatype.XMLGregorianCalendar; + +/** + * Typical processor "component-level" method that performs a well defined part of the computation. + * This is the full version for projection-level, i.e. with projection context and activityDescription. + * + * @param Fake type parameter that is necessary to make type inference in partialProcessorExecute methods happy. + */ +@Experimental +@FunctionalInterface +public interface ProjectionAwareProcessorMethodRef { + + void run(LensContext lensContext, LensProjectionContext projectionContext, String activityDescription, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PreconditionViolationException; + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/SimplifiedProcessorMethodRef.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/SimplifiedProcessorMethodRef.java new file mode 100644 index 00000000000..b855129db4d --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/SimplifiedProcessorMethodRef.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 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.util; + +import com.evolveum.midpoint.model.impl.lens.LensContext; +import com.evolveum.midpoint.repo.api.PreconditionViolationException; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.util.annotation.Experimental; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + +import javax.xml.datatype.XMLGregorianCalendar; + +/** + * Typical processor "component-level" method that performs a well defined part of the computation. + * This is the simplified version, i.e. without activityDescription. + * + * @param Fake type parameter that is necessary to make type inference in partialProcessorExecute methods happy. + */ +@Experimental +@FunctionalInterface +public interface SimplifiedProcessorMethodRef { + + void run(LensContext lensContext, XMLGregorianCalendar now, Task task, OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + SecurityViolationException, PolicyViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException, PreconditionViolationException; + +} diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/SkipWhenFocusDeleted.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/SkipWhenFocusDeleted.java new file mode 100644 index 00000000000..c0ca231db56 --- /dev/null +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/SkipWhenFocusDeleted.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020 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.util; + +/** + * Should we skip the processing if the focus is going to be deleted? + */ +public enum SkipWhenFocusDeleted { + + /** + * No. Processor should be always executed. + */ + NONE, + + /** + * Yes. Processor should be skipped if the primary delta is DELETE. + * TODO Do we really need this? It was created by inspecting existing code. + * Maybe NONE + PRIMARY_OR_SECONDARY (i.e. simple false/true flag) is sufficient. + */ + PRIMARY, + + /** + * Yes. Processor should be skipped if the primary or secondary delta is DELETE. + */ + PRIMARY_OR_SECONDARY +} diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/security/AbstractSecurityTest.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/security/AbstractSecurityTest.java index f32e0a5c1fd..d402efb124b 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/security/AbstractSecurityTest.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/security/AbstractSecurityTest.java @@ -497,7 +497,7 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti setDefaultObjectTemplate(UserType.COMPLEX_TYPE, USER_TEMPLATE_SECURITY_OID, initResult); - InternalsConfig.setDetailedAuhotizationLog(true); + InternalsConfig.setDetailedAuthorizationLog(true); } protected int getNumberOfRoles() { diff --git a/model/model-intest/src/test/resources/logback-test.xml b/model/model-intest/src/test/resources/logback-test.xml index da372435a13..43e787318b8 100644 --- a/model/model-intest/src/test/resources/logback-test.xml +++ b/model/model-intest/src/test/resources/logback-test.xml @@ -62,7 +62,7 @@ - + diff --git a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportManagerImpl.java b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportManagerImpl.java index 13914d9e491..3567587fe24 100644 --- a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportManagerImpl.java +++ b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportManagerImpl.java @@ -107,24 +107,17 @@ public void init() { } @Override - public void invoke(PrismObject object, - Collection> options, Task task, - OperationResult parentResult) throws SchemaException, - ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException { + public void invoke(PrismObject object, Collection> options, + Task task, OperationResult parentResult) throws SchemaException { - if (!ReportType.class.equals(object.getCompileTimeClass())) { - return; - } - - boolean raw = isRaw(options); - if (!raw) { + if (ReportType.class.equals(object.getCompileTimeClass()) && !isRaw(options)) { + //noinspection unchecked ReportTypeUtil.applyDefinition((PrismObject) object, prismContext); } } private boolean isRaw(Collection> options) { - GetOperationOptions rootOptions = SelectorOptions.findRootOptions(options); - return rootOptions == null ? false : GetOperationOptions.isRaw(rootOptions); + return GetOperationOptions.isRaw(SelectorOptions.findRootOptions(options)); } /** @@ -190,14 +183,10 @@ private boolean isDashboarReport(PrismObject object) { public HookOperationMode invoke(@NotNull ModelContext context, @NotNull Task task, @NotNull OperationResult parentResult) { ModelState state = context.getState(); if (state != ModelState.FINAL) { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("report manager called in state = " + state + ", exiting."); - } + LOGGER.trace("report manager called in state = {}, exiting.", state); return HookOperationMode.FOREGROUND; } else { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("report manager called in state = " + state + ", proceeding."); - } + LOGGER.trace("report manager called in state = {}, proceeding.", state); } boolean relatesToReport = false; diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/hook/WfHook.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/hook/WfHook.java index cad1b3eff37..7eda22b990b 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/hook/WfHook.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/hook/WfHook.java @@ -96,7 +96,6 @@ public HookOperationMode invoke(@NotNull ModelContext result.addArbitraryObjectAsContext("model state", context.getState()); try { WfConfigurationType wfConfigurationType = configurationHelper.getWorkflowConfiguration(context, result); - // TODO consider this if it's secure enough if (wfConfigurationType != null && Boolean.FALSE.equals(wfConfigurationType.isModelHookEnabled())) { LOGGER.info("Workflow model hook is disabled. Proceeding with operation execution as if everything is approved."); result.recordSuccess(); diff --git a/repo/security-enforcer-impl/src/main/java/com/evolveum/midpoint/security/enforcer/impl/SecurityEnforcerImpl.java b/repo/security-enforcer-impl/src/main/java/com/evolveum/midpoint/security/enforcer/impl/SecurityEnforcerImpl.java index a4a86d11c05..9bdcff7cb3d 100644 --- a/repo/security-enforcer-impl/src/main/java/com/evolveum/midpoint/security/enforcer/impl/SecurityEnforcerImpl.java +++ b/repo/security-enforcer-impl/src/main/java/com/evolveum/midpoint/security/enforcer/impl/SecurityEnforcerImpl.java @@ -338,7 +338,7 @@ private AccessDecision determine private void logSubitemContainerDecision(AccessDecision subdecision, String location, PrismContainerValue cval) { if (LOGGER.isTraceEnabled()) { - if (subdecision != AccessDecision.ALLOW || InternalsConfig.isDetailedAuhotizationLog()) { + if (subdecision != AccessDecision.ALLOW || InternalsConfig.isDetailedAuthorizationLog()) { LOGGER.trace(" container {} for {} (processed subitems): decision={}", cval.getPath(), location, subdecision); } } @@ -346,7 +346,7 @@ private void logSubitemContainerDecision(AccessDecisio private void logSubitemDecision(AccessDecision subdecision, String location, ItemPath path) { if (LOGGER.isTraceEnabled()) { - if (subdecision != AccessDecision.ALLOW || InternalsConfig.isDetailedAuhotizationLog()) { + if (subdecision != AccessDecision.ALLOW || InternalsConfig.isDetailedAuthorizationLog()) { LOGGER.trace(" item {} for {}: decision={}", path, location, subdecision); } } diff --git a/testing/story/src/test/resources/logback-test.xml b/testing/story/src/test/resources/logback-test.xml index b16814c6a15..f87bfe1b0ed 100644 --- a/testing/story/src/test/resources/logback-test.xml +++ b/testing/story/src/test/resources/logback-test.xml @@ -59,7 +59,7 @@ - +