From 2c98457f9f5d49ccede487d57c8fdb954d9f9343 Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Thu, 23 Apr 2020 13:42:58 +0200 Subject: [PATCH] Improve Clockwork and Projector code a bit The goal of this work is to make the structure of the processing in Projector (and partially also in Clockwork) more obvious from the source code. 1. Eliminated the wave loop from Projector.projectInternal; moving it into Projector.projectAllWaves. (Note that the projector wave loop is used only for previewing changes.) 2. AssignmentHolderProcessor was cleaned up significantly, factoring out e.g. iteration functionality into IterationHelper. 3. Common entry code in processors (checking focus type and existence, casting LensContext) was factored out to ClockworkMedic, using experimental ProcessorExecution annotation. 4. Common post-processing in processors (integrity checking, cross-component updating, diag logging) was moved to processors themselves, simplifying the orchestration code. 5. PolicyRuleEnforcer.execute and policyRuleSuspendTaskExecutor.execute calls were moved to more appropriate places. 6. FocusProcessor was renamed to FocusActivationProcessor. A couple of other minor changes were done as well, with (presumably) negligible impact on the functionality. --- .../component/progress/ProgressReporter.java | 2 +- .../schema/internals/InternalsConfig.java | 14 +- .../model/api/util/ClockworkInspector.java | 2 +- .../midpoint/model/impl/lens/Clockwork.java | 181 +- .../model/impl/lens/ClockworkMedic.java | 136 +- .../midpoint/model/impl/lens/LensContext.java | 56 +- .../model/impl/lens/LensFocusContext.java | 9 +- .../impl/lens/LensProjectionContext.java | 3024 +++++++++-------- .../midpoint/model/impl/lens/LensUtil.java | 2336 ++++++------- .../lens/projector/ActivationProcessor.java | 116 +- .../model/impl/lens/projector/Components.java | 36 + .../projector/ConsolidationProcessor.java | 17 +- .../impl/lens/projector/ContextLoader.java | 41 +- .../lens/projector/DependencyProcessor.java | 65 +- .../lens/projector/OutboundProcessor.java | 3 + .../projector/ProjectionValuesProcessor.java | 205 +- .../model/impl/lens/projector/Projector.java | 315 +- .../lens/projector/ProjectorProcessor.java | 22 + .../projector/ReconciliationProcessor.java | 54 +- .../credentials/CredentialsProcessor.java | 11 +- .../ProjectionCredentialsProcessor.java | 41 +- .../focus/AssignmentHolderProcessor.java | 867 +---- .../projector/focus/AssignmentProcessor.java | 102 +- ...sor.java => FocusActivationProcessor.java} | 115 +- .../focus/FocusLifecycleProcessor.java | 370 +- .../projector/focus/InboundProcessor.java | 61 +- .../focus/ItemLimitationsChecker.java | 81 + .../lens/projector/focus/IterationHelper.java | 360 ++ .../focus/ObjectTemplateProcessor.java | 35 +- .../projector/policy/PolicyRuleEnforcer.java | 4 +- .../projector/policy/PolicyRuleProcessor.java | 15 +- .../projector/util/ProcessorExecution.java | 52 + .../lens/projector/util/ProcessorMethod.java | 31 + .../projector/util/ProcessorMethodRef.java | 32 + .../ProjectionAwareProcessorMethodRef.java | 33 + .../util/SimplifiedProcessorMethodRef.java | 32 + .../projector/util/SkipWhenFocusDeleted.java | 31 + .../intest/security/AbstractSecurityTest.java | 2 +- .../src/test/resources/logback-test.xml | 2 +- .../report/impl/ReportManagerImpl.java | 25 +- .../midpoint/wf/impl/hook/WfHook.java | 1 - .../enforcer/impl/SecurityEnforcerImpl.java | 4 +- .../story/src/test/resources/logback-test.xml | 2 +- 43 files changed, 4469 insertions(+), 4474 deletions(-) create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/Components.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/ProjectorProcessor.java rename model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/{FocusProcessor.java => FocusActivationProcessor.java} (82%) create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/ItemLimitationsChecker.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/IterationHelper.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorExecution.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorMethod.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProcessorMethodRef.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/ProjectionAwareProcessorMethodRef.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/SimplifiedProcessorMethodRef.java create mode 100644 model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/util/SkipWhenFocusDeleted.java 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 @@ - +