diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentTablePanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentTablePanel.java index 4eb63704292..58163fd6dc4 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentTablePanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/AssignmentTablePanel.java @@ -159,10 +159,18 @@ public boolean isVisible(){ protected void populateAssignmentDetailsPanel(ListItem item){ AssignmentEditorPanel editor = new AssignmentEditorPanel(ID_ROW, item.getModel()){ + private static final long serialVersionUID = 1L; + @Override protected boolean ignoreMandatoryAttributes(){ return AssignmentTablePanel.this.ignoreMandatoryAttributes(); } + + @Override + protected boolean isRelationEditable(){ + return AssignmentTablePanel.this.isRelationEditable(); + } + }; item.add(editor); @@ -299,6 +307,10 @@ public void onClick(AjaxRequestTarget target) { return items; } + protected boolean isRelationEditable(){ + return true; + } + protected void showAllAssignments(AjaxRequestTarget target) { } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/InducedEntitlementsPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/InducedEntitlementsPanel.java index b1af5f02c6c..ba879dd2a66 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/InducedEntitlementsPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/assignment/InducedEntitlementsPanel.java @@ -20,6 +20,7 @@ import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.web.page.admin.PageAdminObjectDetails; import com.evolveum.midpoint.web.util.validation.MidpointFormValidator; +import com.evolveum.midpoint.web.util.validation.MidpointFormValidatorImpl; import com.evolveum.midpoint.web.util.validation.SimpleValidationError; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; @@ -80,7 +81,10 @@ public InducedEntitlementsPanel(String id, IModel validateObject(PrismObject object, Collection> deltas) { List errors = new ArrayList(); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/self/PageAssignmentsList.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/self/PageAssignmentsList.java index 20fa26a37d4..2d507be1543 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/self/PageAssignmentsList.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/self/PageAssignmentsList.java @@ -117,6 +117,8 @@ public void initLayout() { AssignmentTablePanel panel = new AssignmentTablePanel(ID_ASSIGNMENT_TABLE_PANEL, assignmentsModel){ + private static final long serialVersionUID = 1L; + @Override protected List createAssignmentMenu() { List items = new ArrayList<>(); @@ -138,11 +140,17 @@ public void onClick(AjaxRequestTarget target) { items.add(item); return items; } - + @Override - public IModel getLabel() { - return createStringResource("PageAssignmentsList.assignmentsToRequest"); - } + public IModel getLabel() { + return createStringResource("PageAssignmentsList.assignmentsToRequest"); + } + + @Override + protected boolean isRelationEditable() { + return false; + } + }; mainForm.add(panel); @@ -153,23 +161,23 @@ public List getObject() { return getSessionStorage().getRoleCatalog().getTargetUserList(); } }, - true, createStringResource("AssignmentCatalogPanel.selectTargetUser")){ + true, createStringResource("AssignmentCatalogPanel.selectTargetUser")) { private static final long serialVersionUID = 1L; @Override - protected String getUserButtonLabel(){ + protected String getUserButtonLabel() { return getTargetUserSelectionButtonLabel(getModelObject()); } @Override - protected void onDeleteSelectedUsersPerformed(AjaxRequestTarget target){ + protected void onDeleteSelectedUsersPerformed(AjaxRequestTarget target) { super.onDeleteSelectedUsersPerformed(target); getSessionStorage().getRoleCatalog().setTargetUserList(new ArrayList<>()); targetUserChangePerformed(target); } @Override - protected void multipleUsersSelectionPerformed(AjaxRequestTarget target, List usersList){ + protected void multipleUsersSelectionPerformed(AjaxRequestTarget target, List usersList) { getSessionStorage().getRoleCatalog().setTargetUserList(usersList); targetUserChangePerformed(target); } @@ -278,9 +286,7 @@ private void onSingleUserRequestPerformed(AjaxRequestTarget target) { result.recomputeStatus(); } - findBackgroundTaskOperation(result); - if (backgroundTaskOperationResult != null - && StringUtils.isNotEmpty(backgroundTaskOperationResult.getAsynchronousOperationReference())){ + if (hasBackgroundTaskOperation(result)){ result.setMessage(createStringResource("PageAssignmentsList.requestInProgress").getString()); showResult(result); clearStorage(); @@ -340,9 +346,7 @@ private void onMultiUserRequestPerformed(AjaxRequestTarget target) { "Failed to execute operaton " + result.getOperation(), e); target.add(getFeedbackPanel()); } - findBackgroundTaskOperation(result); - if (backgroundTaskOperationResult != null - && StringUtils.isNotEmpty(backgroundTaskOperationResult.getAsynchronousOperationReference())) { + if (hasBackgroundTaskOperation(result)) { result.setMessage(createStringResource("PageAssignmentsList.requestInProgress").getString()); showResult(result); clearStorage(); @@ -411,24 +415,9 @@ private ContainerDelta handleAssignmentDeltas(ObjectDelta focusDelta, } - private void findBackgroundTaskOperation(OperationResult result){ - if (backgroundTaskOperationResult != null) { - return; - } else { - List subresults = result.getSubresults(); - if (subresults == null || subresults.size() == 0) { - return; - } - for (OperationResult subresult : subresults) { - if (subresult.getOperation().equals(OPERATION_WF_TASK_CREATED)) { - backgroundTaskOperationResult = subresult; - return; - } else { - findBackgroundTaskOperation(subresult); - } - } - } - return; + private boolean hasBackgroundTaskOperation(OperationResult result){ + String caseOid = OperationResult.referenceToCaseOid(result.findAsynchronousOperationReference()); + return StringUtils.isNotEmpty(caseOid); } private void handleModifyAssignmentDelta(AssignmentEditorDto assDto, diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/util/validation/MidpointFormValidatorImpl.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/util/validation/MidpointFormValidatorImpl.java index 20ea088a598..b0ddbf27c4f 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/util/validation/MidpointFormValidatorImpl.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/util/validation/MidpointFormValidatorImpl.java @@ -14,6 +14,7 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; import com.evolveum.prism.xml.ns._public.types_3.ItemPathType; +import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -23,7 +24,9 @@ * * @author shood * */ -public class MidpointFormValidatorImpl implements MidpointFormValidator { +public class MidpointFormValidatorImpl implements MidpointFormValidator, Serializable { + + private static final long serialVersionUID = 1L; @Override public Collection validateObject(PrismObject object, Collection> deltas) { diff --git a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/equivalence/ParameterizedEquivalenceStrategy.java b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/equivalence/ParameterizedEquivalenceStrategy.java index 33118ecddb0..f21187991eb 100644 --- a/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/equivalence/ParameterizedEquivalenceStrategy.java +++ b/infra/prism-api/src/main/java/com/evolveum/midpoint/prism/equivalence/ParameterizedEquivalenceStrategy.java @@ -27,7 +27,7 @@ public class ParameterizedEquivalenceStrategy implements EquivalenceStrategy { /** * The (almost) highest level of recognition. Useful e.g. for comparing values for the purpose of XML editing. - * Still, ignores e.g. definitions, parent objects, immutability flag, etc. + * Still, ignores e.g. definitions, parent objects, origin, immutability flag, etc. * * Corresponds to pre-4.0 flags ignoreMetadata = false, literal = true. */ @@ -39,7 +39,7 @@ public class ParameterizedEquivalenceStrategy implements EquivalenceStrategy { * * Currently this is the default for equals/hashCode. * - * Corresponds to pre-4.0 flags ignoreMetadata = false, literal = false. + * Roughly corresponds to pre-4.0 flags ignoreMetadata = false, literal = false. */ public static final ParameterizedEquivalenceStrategy NOT_LITERAL; @@ -101,7 +101,7 @@ public class ParameterizedEquivalenceStrategy implements EquivalenceStrategy { LITERAL.consideringOperationalData = true; LITERAL.consideringContainerIds = true; LITERAL.consideringDifferentContainerIds = true; - LITERAL.consideringValueOrigin = true; + LITERAL.consideringValueOrigin = false; LITERAL.consideringReferenceFilters = true; LITERAL.compareElementNames = true; putIntoNiceNames(LITERAL, "LITERAL"); @@ -111,7 +111,7 @@ public class ParameterizedEquivalenceStrategy implements EquivalenceStrategy { NOT_LITERAL.consideringOperationalData = true; NOT_LITERAL.consideringContainerIds = true; NOT_LITERAL.consideringDifferentContainerIds = true; - NOT_LITERAL.consideringValueOrigin = true; + NOT_LITERAL.consideringValueOrigin = false; NOT_LITERAL.consideringReferenceFilters = true; NOT_LITERAL.compareElementNames = true; putIntoNiceNames(NOT_LITERAL, "NOT_LITERAL"); diff --git a/infra/prism-api/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/DeltaSetTripleType.java b/infra/prism-api/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/DeltaSetTripleType.java index b58dd6d79e8..591f0614b9f 100644 --- a/infra/prism-api/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/DeltaSetTripleType.java +++ b/infra/prism-api/src/main/java/com/evolveum/prism/xml/ns/_public/types_3/DeltaSetTripleType.java @@ -29,7 +29,7 @@ "plus", "minus" }) -public class DeltaSetTripleType implements Serializable, JaxbVisitable { +public class DeltaSetTripleType implements Serializable, JaxbVisitable, Cloneable { @XmlElement @Raw diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismObjectValueImpl.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismObjectValueImpl.java index 2d0e7054661..259ed7f3172 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismObjectValueImpl.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismObjectValueImpl.java @@ -116,7 +116,7 @@ public boolean equals(Object o) { // TODO consider the strategy @Override - public int hashCode(ParameterizedEquivalenceStrategy strategy) { + public int hashCode(@NotNull ParameterizedEquivalenceStrategy strategy) { return Objects.hash(super.hashCode(strategy), oid); } diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/result/OperationResult.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/result/OperationResult.java index 2f23d75da97..7c1aafd94fe 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/result/OperationResult.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/result/OperationResult.java @@ -153,6 +153,12 @@ public class OperationResult implements Serializable, DebugDumpable, ShortDumpab private boolean collectingLogEntries; // NOT SERIALIZED private boolean startedLoggingOverride; // NOT SERIALIZED + /** + * After a trace rooted at this operation result is stored, the dictionary that was extracted is stored here. + * It is necessary to correctly interpret traces in this result and its subresults. + */ + private TraceDictionaryType extractedDictionary; // NOT SERIALIZED + private final List traces = new ArrayList<>(); // Caller can specify the reason of the operation invocation. @@ -2414,4 +2420,12 @@ public void setCallerReason(String callerReason) { public List getLogSegments() { return logSegments; } + + public TraceDictionaryType getExtractedDictionary() { + return extractedDictionary; + } + + public void setExtractedDictionary(TraceDictionaryType extractedDictionary) { + this.extractedDictionary = extractedDictionary; + } } diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ObjectTypeUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ObjectTypeUtil.java index c5537234121..b039161a238 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ObjectTypeUtil.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ObjectTypeUtil.java @@ -571,7 +571,7 @@ public static ObjectType toObjectable(PrismObject object) { return object != null ? (ObjectType) object.asObjectable() : null; } - public static PrismObject toPrismObject(T objectable) { + public static PrismObject asPrismObject(T objectable) { //noinspection unchecked return objectable != null ? objectable.asPrismObject() : null; } diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd index 666f4ff99b3..941bf7a697e 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-model-context-3.xsd @@ -813,6 +813,26 @@ + + + + Lens (model) context at input (text dump). + + + 4.0.1 + + + + + + + Lens (model) context at output (text dump). + + + 4.0.1 + + + @@ -2166,6 +2186,16 @@ + + + + Identifier of the dictionary. Used when multiple dictionaries are to be merged. + + + 4.0.1 + + + @@ -2190,13 +2220,20 @@ - + Entry identifier. + + + + + + Identifier of the dictionary in which this entry was originally created. + - true + 4.0.1 diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-policy-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-policy-3.xsd index ee34b586a2e..ae0a1fd345d 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-policy-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-policy-3.xsd @@ -2235,7 +2235,7 @@ for activation in these hardcoded states ("undefined" for active and deprecated states, "archived" for archived state, "disabled" for all other states). To turn off this default behaviour those hardcoded lifecycle states need to be explicitly defined in the state model - and the forcedActivationStatus property shoule be left undefined. + and the forcedActivationStatus property should be left undefined.

@@ -2249,10 +2249,10 @@

There are cases when you need to force midpoint thinks that user has assigned some - role. The assignemnt actually doesn't exist but there is a need to preted as it does. + role. The assignment actually doesn't exist but there is a need to pretend as it does. This can be used e.g. for post-authentication flow. The user has assigned all business, application, etc. roles but we don't want to consider these roles during his - post-authentication proces. Instead, we want to pretend he has "temporary" role assigned + post-authentication process. Instead, we want to pretend he has "temporary" role assigned which allows him to perform post-authentication.

@@ -2340,10 +2340,10 @@

There are cases when you need to force midpoint thinks that user has assigned some - role. The assignemnt actually doesn't exist but there is a need to preted as it does. + role. The assignment actually doesn't exist but there is a need to pretend as it does. This can be used e.g. for post-authentication flow. The user has assigned all business, application, etc. roles but we don't want to consider these roles during his - post-authentication proces. Instead, we want to pretend he has "temporary" role assigned + post-authentication process. Instead, we want to pretend he has "temporary" role assigned which allows him to perform post-authentication.

diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java index ad270b2762f..2819e2573a4 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/controller/ModelController.java @@ -2309,24 +2309,23 @@ public void notifyChange(ResourceObjectShadowChangeDescriptionType changeDescrip PrismObject oldShadow; LOGGER.trace("resolving old object"); if (!StringUtils.isEmpty(oldShadowOid)) { + // FIXME we should not get object from resource here: it should be sufficient to retrieve object from the repository + // (and even that can be skipped, if identifiers are correctly set) ... MID-5834 oldShadow = getObject(ShadowType.class, oldShadowOid, SelectorOptions.createCollection(GetOperationOptions.createDoNotDiscovery()), task, parentResult); - eventDescription.setOldShadow(oldShadow); + eventDescription.setOldRepoShadow(oldShadow); LOGGER.trace("old object resolved to: {}", oldShadow.debugDumpLazily()); } else { LOGGER.trace("Old shadow null"); } - PrismObject currentShadow = null; - ShadowType currentShadowType = changeDescription.getCurrentShadow(); - LOGGER.trace("resolving current shadow"); - if (currentShadowType != null) { - prismContext.adopt(currentShadowType); - currentShadow = currentShadowType.asPrismObject(); - LOGGER.trace("current shadow resolved to {}", currentShadow.debugDumpLazily()); + ShadowType currentResourceObjectBean = changeDescription.getCurrentShadow(); + if (currentResourceObjectBean != null) { + PrismObject currentResourceObject = currentResourceObjectBean.asPrismObject(); + prismContext.adopt(currentResourceObject); + LOGGER.trace("current resource object:\n{}", currentResourceObject.debugDumpLazily()); + eventDescription.setCurrentResourceObject(currentResourceObject); } - eventDescription.setCurrentShadow(currentShadow); - ObjectDeltaType deltaType = changeDescription.getObjectDelta(); if (deltaType != null) { 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 ee25e58770b..040e9167d1f 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 @@ -236,7 +236,7 @@ public HookOperationMode run(LensContext context, Task } finally { recordTraceAtEnd(context, trace, result); if (tracingRequested) { - tracer.storeTrace(task, result); + tracer.storeTrace(task, result, parentResult); TracingAppender.terminateCollecting(); // todo reconsider LevelOverrideTurboFilter.cancelLoggingOverride(); // todo reconsider } @@ -266,10 +266,7 @@ private boolean startTracingIfRequested(LensContext co private ClockworkRunTraceType recordTraceAtStart(LensContext context, Task task, OperationResult result) throws SchemaException { ClockworkRunTraceType trace = new ClockworkRunTraceType(prismContext); - TracingLevelType level = result.getTracingLevel(trace.getClass()); - if (level.ordinal() >= TracingLevelType.MINIMAL.ordinal()) { - trace.getText().add(context.debugDump()); // todo - } + trace.setInputLensContextText(context.debugDump()); trace.setInputLensContext(context.toLensContextType(getExportTypeTraceOrReduced(trace, result))); result.addTrace(trace); return trace; @@ -278,6 +275,7 @@ private ClockworkRunTraceType recordTraceAtStart(LensCont private void recordTraceAtEnd(LensContext context, ClockworkRunTraceType trace, OperationResult result) throws SchemaException { if (trace != null) { + trace.setOutputLensContextText(context.debugDump()); trace.setOutputLensContext(context.toLensContextType(getExportTypeTraceOrReduced(trace, result))); if (context.getFocusContext() != null) { // todo reconsider this PrismObject objectAny = context.getFocusContext().getObjectAny(); @@ -537,6 +535,9 @@ public HookOperationMode click(LensContext context, Ta ClockworkClickTraceType trace; if (result.isTraced()) { trace = new ClockworkClickTraceType(prismContext); + if (result.isTracingNormal(ClockworkClickTraceType.class)) { + trace.setInputLensContextText(context.debugDump()); + } trace.setInputLensContext(context.toLensContextType(getExportType(trace, result))); result.getTraces().add(trace); } else { @@ -627,6 +628,9 @@ public HookOperationMode click(LensContext context, Ta throw e; } finally { if (trace != null) { + if (result.isTracingNormal(ClockworkClickTraceType.class)) { + trace.setOutputLensContextText(context.debugDump()); + } trace.setOutputLensContext(context.toLensContextType(getExportType(trace, result))); } result.computeStatusIfUnknown(); 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 408509e07c4..712fac566af 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 @@ -195,6 +195,9 @@ public void partialExecute(String baseComponentName, ProjectorComponentRunnable ProjectorComponentTraceType trace; if (result.isTraced()) { trace = new ProjectorComponentTraceType(); + if (result.isTracingNormal(ProjectorComponentTraceType.class)) { + trace.setInputLensContextText(context.debugDump()); + } trace.setInputLensContext(context.toLensContextType(getExportType(trace, result))); result.addTrace(trace); } else { @@ -215,6 +218,9 @@ public void partialExecute(String baseComponentName, ProjectorComponentRunnable } finally { result.computeStatusIfUnknown(); if (trace != null) { + if (result.isTracingNormal(ProjectorComponentTraceType.class)) { + trace.setOutputLensContextText(context.debugDump()); + } trace.setOutputLensContext(context.toLensContextType(getExportType(trace, result))); } if (clockworkInspector != null) { 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 ac8a14cde8e..24e8667564a 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 @@ -107,6 +107,9 @@ public void load(LensContext context, String activityD ProjectorComponentTraceType trace; if (result.isTraced()) { trace = new ProjectorComponentTraceType(prismContext); + if (result.isTracingNormal(ProjectorComponentTraceType.class)) { + trace.setInputLensContextText(context.debugDump()); + } trace.setInputLensContext(context.toLensContextType(getExportType(trace, result))); result.addTrace(trace); } else { @@ -194,6 +197,9 @@ public void load(LensContext context, String activityD throw e; } finally { if (trace != null) { + if (result.isTracingNormal(ProjectorComponentTraceType.class)) { + trace.setOutputLensContextText(context.debugDump()); + } trace.setOutputLensContext(context.toLensContextType(getExportType(trace, result))); } } @@ -327,6 +333,9 @@ public void determineFocusContext(LensContext context, FocusLoadedTraceType trace; if (result.isTraced()) { trace = new FocusLoadedTraceType(); + if (result.isTracingNormal(FocusLoadedTraceType.class)) { + trace.setInputLensContextText(context.debugDump()); + } trace.setInputLensContext(context.toLensContextType(getExportType(trace, result))); result.addTrace(trace); } else { @@ -392,6 +401,9 @@ public void determineFocusContext(LensContext context, throw t; } finally { if (trace != null) { + if (result.isTracingNormal(FocusLoadedTraceType.class)) { + trace.setOutputLensContextText(context.debugDump()); + } trace.setOutputLensContext(context.toLensContextType(getExportType(trace, result))); } result.computeStatusIfUnknown(); @@ -1450,6 +1462,9 @@ public void loadFullShadow(LensContext context, LensPr FullShadowLoadedTraceType trace; if (result.isTraced()) { trace = new FullShadowLoadedTraceType(prismContext); + if (result.isTracingNormal(FullShadowLoadedTraceType.class)) { + trace.setInputLensContextText(context.debugDump()); + } trace.setInputLensContext(context.toLensContextType(getExportType(trace, result))); result.addTrace(trace); } else { @@ -1518,6 +1533,9 @@ public void loadFullShadow(LensContext context, LensPr throw t; } finally { if (trace != null) { + if (result.isTracingNormal(FullShadowLoadedTraceType.class)) { + trace.setOutputLensContextText(context.debugDump()); + } trace.setOutputLensContext(context.toLensContextType(getExportType(trace, result))); } result.computeStatusIfUnknown(); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationContext.java index 1e5cac2a63a..d7516749650 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationContext.java @@ -56,7 +56,7 @@ public class SynchronizationContext implements DebugDumpabl private PrismObject currentShadow; private PrismObject resource; private PrismObject systemConfiguration; - private String chanel; + private String channel; private ExpressionProfile expressionProfile; private Task task; @@ -81,11 +81,11 @@ public class SynchronizationContext implements DebugDumpabl private PrismContext prismContext; - public SynchronizationContext(PrismObject applicableShadow, PrismObject currentShadow, PrismObject resource, String chanel, PrismContext prismContext, Task task, OperationResult result) { + public SynchronizationContext(PrismObject applicableShadow, PrismObject currentShadow, PrismObject resource, String channel, PrismContext prismContext, Task task, OperationResult result) { this.applicableShadow = applicableShadow; this.currentShadow = currentShadow; this.resource = resource; - this.chanel = chanel; + this.channel = channel; this.task = task; this.result = result; this.prismContext = prismContext; @@ -125,7 +125,7 @@ public boolean isSatisfyTaskConstraints() throws SchemaException { return isApplicable; } - //TODO multi-threded tasks? + //TODO multi-threaded tasks? private T getTaskPropertyValue(QName propertyName) { PrismProperty prop = task.getExtensionPropertyOrClone(ItemName.fromQName(propertyName)); if (prop == null || prop.isEmpty()) { @@ -197,19 +197,18 @@ public SynchronizationReactionType getReaction() throws ConfigurationException { throw new ConfigurationException("No situation defined for a reaction in " + resource); } if (reactionSituation.equals(situation)) { - if (syncReaction.getChannel() != null && !syncReaction.getChannel().isEmpty()) { + if (syncReaction.getChannel().isEmpty()) { + defaultReaction = syncReaction; + } else { if (syncReaction.getChannel().contains("") || syncReaction.getChannel().contains(null)) { defaultReaction = syncReaction; } - if (syncReaction.getChannel().contains(chanel)) { + if (syncReaction.getChannel().contains(channel)) { reaction = syncReaction; return reaction; } else { - LOGGER.trace("Skipping reaction {} because the channel does not match {}", reaction, chanel); - continue; + LOGGER.trace("Skipping reaction {} because the channel does not match {}", reaction, channel); } - } else { - defaultReaction = syncReaction; } } } @@ -243,8 +242,8 @@ public Boolean isDoReconciliation() { } public Boolean isLimitPropagation() { - if (StringUtils.isNotBlank(chanel)) { - QName channelQName = QNameUtil.uriToQName(chanel); + if (StringUtils.isNotBlank(channel)) { + QName channelQName = QNameUtil.uriToQName(channel); // Discovery channel is used when compensating some inconsistent // state. Therefore we do not want to propagate changes to other // resources. We only want to resolve the problem and continue in @@ -344,8 +343,8 @@ public void setSituation(SynchronizationSituationType situation) { public PrismObject getSystemConfiguration() { return systemConfiguration; } - public String getChanel() { - return chanel; + public String getChannel() { + return channel; } public void setApplicableShadow(PrismObject applicableShadow) { this.applicableShadow = applicableShadow; @@ -359,8 +358,8 @@ public void setResource(PrismObject resource) { public void setSystemConfiguration(PrismObject systemConfiguration) { this.systemConfiguration = systemConfiguration; } - public void setChanel(String chanel) { - this.chanel = chanel; + public void setChannel(String channel) { + this.channel = channel; } // public SynchronizationReactionType getReaction() { @@ -448,7 +447,7 @@ public String debugDump(int indent) { DebugUtil.debugDumpWithLabelLn(sb, "currentShadow", currentShadow, indent + 1); DebugUtil.debugDumpWithLabelToStringLn(sb, "resource", resource, indent + 1); DebugUtil.debugDumpWithLabelToStringLn(sb, "systemConfiguration", systemConfiguration, indent + 1); - DebugUtil.debugDumpWithLabelToStringLn(sb, "chanel", chanel, indent + 1); + DebugUtil.debugDumpWithLabelToStringLn(sb, "channel", channel, indent + 1); DebugUtil.debugDumpWithLabelToStringLn(sb, "expressionProfile", expressionProfile, indent + 1); DebugUtil.debugDumpWithLabelToStringLn(sb, "objectSynchronization", objectSynchronization, indent + 1); DebugUtil.debugDumpWithLabelLn(sb, "focusClass", focusClass, indent + 1); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationServiceImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationServiceImpl.java index 77c9f08c3a7..cce1b42ecb4 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationServiceImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/sync/SynchronizationServiceImpl.java @@ -322,7 +322,7 @@ private ObjectSynchronizationDiscriminatorType evaluateSyn String desc = "synchronization divider type "; ExpressionVariables variables = ModelImplUtils.getDefaultExpressionVariables(null, syncCtx.getApplicableShadow(), null, syncCtx.getResource(), syncCtx.getSystemConfiguration(), null, syncCtx.getPrismContext()); - variables.put(ExpressionConstants.VAR_CHANNEL, syncCtx.getChanel(), String.class); + variables.put(ExpressionConstants.VAR_CHANNEL, syncCtx.getChannel(), String.class); try { ModelExpressionThreadLocalHolder.pushExpressionEnvironment(new ExpressionEnvironment<>(task, result)); //noinspection unchecked @@ -380,7 +380,7 @@ private boolean checkSynchronizationPolicy(Synchronization if (!syncCtx.hasApplicablePolicy()) { String message = "SYNCHRONIZATION no matching policy for " + syncCtx.getApplicableShadow() + " (" + syncCtx.getApplicableShadow().asObjectable().getObjectClass() + ") " + " on " + syncCtx.getResource() - + ", ignoring change from channel " + syncCtx.getChanel(); + + ", ignoring change from channel " + syncCtx.getChannel(); LOGGER.debug(message); List> modifications = createShadowIntentAndSynchronizationTimestampDelta(syncCtx, false); executeShadowModifications(syncCtx.getApplicableShadow(), modifications, task, subResult); @@ -392,7 +392,7 @@ private boolean checkSynchronizationPolicy(Synchronization if (!syncCtx.isSynchronizationEnabled()) { String message = "SYNCHRONIZATION is not enabled for " + syncCtx.getResource() - + " ignoring change from channel " + syncCtx.getChanel(); + + " ignoring change from channel " + syncCtx.getChannel(); LOGGER.debug(message); List> modifications = createShadowIntentAndSynchronizationTimestampDelta(syncCtx, true); executeShadowModifications(syncCtx.getApplicableShadow(), modifications, task, subResult); diff --git a/model/model-intest/src/test/resources/logback-test.xml b/model/model-intest/src/test/resources/logback-test.xml index e9c8745cf89..940c95dacd8 100644 --- a/model/model-intest/src/test/resources/logback-test.xml +++ b/model/model-intest/src/test/resources/logback-test.xml @@ -87,7 +87,7 @@ - + diff --git a/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java b/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java index 7178824f503..5f86a51e781 100644 --- a/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java +++ b/model/model-test/src/main/java/com/evolveum/midpoint/model/test/AbstractModelIntegrationTest.java @@ -1642,7 +1642,7 @@ protected PrismObject getUserFromRepo(String userOid) throws ObjectNot } protected PrismObject findObjectByName(Class type, String name) throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { - Task task = taskManager.createTaskInstance(AbstractModelIntegrationTest.class.getName() + ".findObjectByName"); + Task task = getOrCreateTask("findObjectByName"); OperationResult result = task.getResult(); List> objects = modelService.searchObjects(type, createNameQuery(name), null, task, result); if (objects.isEmpty()) { @@ -3601,25 +3601,33 @@ protected void restartTask(String taskOid) throws CommonException { LOGGER.warn("Sleep interrupted: {}", e.getMessage(), e); } - final OperationResult result = new OperationResult(AbstractIntegrationTest.class+".restartTask"); - Task task = taskManager.getTaskWithResult(taskOid, result); - LOGGER.info("Restarting task {}", taskOid); - if (task.getExecutionStatus() == TaskExecutionStatus.SUSPENDED) { - LOGGER.debug("Task {} is suspended, resuming it", task); - taskManager.resumeTask(task, result); - } else if (task.getExecutionStatus() == TaskExecutionStatus.CLOSED) { - LOGGER.debug("Task {} is closed, scheduling it to run now", task); - taskManager.scheduleTasksNow(singleton(taskOid), result); - } else if (task.getExecutionStatus() == TaskExecutionStatus.RUNNABLE) { - if (taskManager.getLocallyRunningTaskByIdentifier(task.getTaskIdentifier()) != null) { - // Task is really executing. Let's wait until it finishes; hopefully it won't start again (TODO) - LOGGER.debug("Task {} is running, waiting while it finishes before restarting", task); - waitForTaskFinish(taskOid, false); + OperationResult result = createSubresult("restartTask"); + try { + Task task = taskManager.getTaskWithResult(taskOid, result); + LOGGER.info("Restarting task {}", taskOid); + if (task.getExecutionStatus() == TaskExecutionStatus.SUSPENDED) { + LOGGER.debug("Task {} is suspended, resuming it", task); + taskManager.resumeTask(task, result); + } else if (task.getExecutionStatus() == TaskExecutionStatus.CLOSED) { + LOGGER.debug("Task {} is closed, scheduling it to run now", task); + taskManager.scheduleTasksNow(singleton(taskOid), result); + } else if (task.getExecutionStatus() == TaskExecutionStatus.RUNNABLE) { + if (taskManager.getLocallyRunningTaskByIdentifier(task.getTaskIdentifier()) != null) { + // Task is really executing. Let's wait until it finishes; hopefully it won't start again (TODO) + LOGGER.debug("Task {} is running, waiting while it finishes before restarting", task); + waitForTaskFinish(taskOid, false); + } + LOGGER.debug("Task {} is finished, scheduling it to run now", task); + taskManager.scheduleTasksNow(singleton(taskOid), result); + } else { + throw new IllegalStateException( + "Task " + task + " cannot be restarted, because its state is: " + task.getExecutionStatus()); } - LOGGER.debug("Task {} is finished, scheduling it to run now", task); - taskManager.scheduleTasksNow(singleton(taskOid), result); - } else { - throw new IllegalStateException("Task " + task + " cannot be restarted, because its state is: " + task.getExecutionStatus()); + } catch (Throwable t) { + result.recordFatalError(t); + throw t; + } finally { + result.computeStatusIfUnknown(); } } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/access/WorkItemManager.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/access/WorkItemManager.java index 5c634bd20d4..2fd340f2aee 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/access/WorkItemManager.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/access/WorkItemManager.java @@ -91,13 +91,14 @@ public void completeWorkItem(WorkItemId workItemId, @NotNull AbstractWorkItemOut throw e; } finally { result.computeStatusIfUnknown(); - storeTraceIfRequested(tracingRequested, task, result); + storeTraceIfRequested(tracingRequested, task, result, parentResult); } } - private void storeTraceIfRequested(boolean tracingRequested, Task task, OperationResult result) { + private void storeTraceIfRequested(boolean tracingRequested, Task task, OperationResult result, + OperationResult parentResult) { if (tracingRequested) { - tracer.storeTrace(task, result); + tracer.storeTrace(task, result, parentResult); TracingAppender.terminateCollecting(); // todo reconsider LevelOverrideTurboFilter.cancelLoggingOverride(); // todo reconsider } @@ -131,7 +132,7 @@ public void completeWorkItems(CompleteWorkItemsRequest request, Task task, Opera throw e; } finally { result.computeStatusIfUnknown(); - storeTraceIfRequested(tracingRequested, task, result); + storeTraceIfRequested(tracingRequested, task, result, parentResult); } } @@ -153,7 +154,7 @@ public void claimWorkItem(WorkItemId workItemId, Task task, OperationResult pare throw e; } finally { result.computeStatusIfUnknown(); - storeTraceIfRequested(tracingRequested, task, result); + storeTraceIfRequested(tracingRequested, task, result, parentResult); } } @@ -175,7 +176,7 @@ public void releaseWorkItem(WorkItemId workItemId, Task task, OperationResult pa throw e; } finally { result.computeStatusIfUnknown(); - storeTraceIfRequested(tracingRequested, task, result); + storeTraceIfRequested(tracingRequested, task, result, parentResult); } } @@ -215,7 +216,7 @@ public void delegateWorkItem(@NotNull WorkItemId workItemId, @NotNull WorkItemDe throw new IllegalStateException(e); } finally { result.computeStatusIfUnknown(); - storeTraceIfRequested(tracingRequested, task, result); + storeTraceIfRequested(tracingRequested, task, result, parentResult); } } diff --git a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/assignments/TestAssignmentsAdvanced.java b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/assignments/TestAssignmentsAdvanced.java index af233d071e4..816f658e1d0 100644 --- a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/assignments/TestAssignmentsAdvanced.java +++ b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/assignments/TestAssignmentsAdvanced.java @@ -1212,7 +1212,7 @@ private void previewAssignRolesToJack(String TEST_NAME, boolean immediate, boole result.computeStatus(); //noinspection ConstantConditions if (TRACE) { - tracer.storeTrace(task, result); + tracer.storeTrace(task, result, null); } // we do not assert success here, because there are (intentional) exceptions in some of the expressions diff --git a/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ResourceEventDescription.java b/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ResourceEventDescription.java index 63ba13e6442..41648f49aff 100644 --- a/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ResourceEventDescription.java +++ b/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ResourceEventDescription.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2010-2019 Evolveum and contributors * * This work is dual-licensed under the Apache License 2.0 @@ -17,19 +17,19 @@ public class ResourceEventDescription implements Serializable, DebugDumpable{ - private PrismObject oldShadow; - private PrismObject currentShadow; + private PrismObject oldRepoShadow; + private PrismObject currentResourceObject; private ObjectDelta delta; private String sourceChannel; // private PrismObject resource; - public PrismObject getCurrentShadow() { - return currentShadow; + public PrismObject getCurrentResourceObject() { + return currentResourceObject; } - public PrismObject getOldShadow() { - return oldShadow; + public PrismObject getOldRepoShadow() { + return oldRepoShadow; } public ObjectDelta getDelta() { @@ -47,12 +47,12 @@ public void setDelta(ObjectDelta delta) { this.delta = delta; } - public void setOldShadow(PrismObject oldShadow) { - this.oldShadow = oldShadow; + public void setOldRepoShadow(PrismObject oldRepoShadow) { + this.oldRepoShadow = oldRepoShadow; } - public void setCurrentShadow(PrismObject currentShadow) { - this.currentShadow = currentShadow; + public void setCurrentResourceObject(PrismObject currentResourceObject) { + this.currentResourceObject = currentResourceObject; } public void setSourceChannel(String sourceChannel) { @@ -60,8 +60,8 @@ public void setSourceChannel(String sourceChannel) { } public boolean isProtected() { - if ((currentShadow != null && ShadowUtil.isProtected(currentShadow)) - || (oldShadow != null && ShadowUtil.isProtected(oldShadow))) { + if ((currentResourceObject != null && ShadowUtil.isProtected(currentResourceObject)) + || (oldRepoShadow != null && ShadowUtil.isProtected(oldRepoShadow))) { return true; } if (delta != null && delta.isAdd() && ShadowUtil.isProtected(delta.getObjectToAdd())) { @@ -72,8 +72,8 @@ public boolean isProtected() { @Override public String toString() { - return "ResourceEventDescription(delta=" + delta + ", currentShadow=" - + SchemaDebugUtil.prettyPrint(currentShadow) + ", oldShadow=" + SchemaDebugUtil.prettyPrint(oldShadow) + ", sourceChannel=" + sourceChannel + return "ResourceEventDescription(delta=" + delta + ", currentResourceObject=" + + SchemaDebugUtil.prettyPrint(currentResourceObject) + ", oldRepoShadow=" + SchemaDebugUtil.prettyPrint(oldRepoShadow) + ", sourceChannel=" + sourceChannel + ")"; } @@ -107,22 +107,22 @@ public String debugDump(int indent) { sb.append("\n"); SchemaDebugUtil.indentDebugDump(sb, indent+1); - sb.append("oldShadow:"); - if (oldShadow == null) { + sb.append("oldRepoShadow:"); + if (oldRepoShadow == null) { sb.append(" null"); } else { - sb.append(oldShadow.debugDump(indent+2)); + sb.append(oldRepoShadow.debugDump(indent+2)); } sb.append("\n"); SchemaDebugUtil.indentDebugDump(sb, indent+1); - sb.append("currentShadow:"); - if (currentShadow == null) { + sb.append("currentResourceObject:"); + if (currentResourceObject == null) { sb.append(" null\n"); } else { sb.append("\n"); - sb.append(currentShadow.debugDump(indent+2)); + sb.append(currentResourceObject.debugDump(indent+2)); } return sb.toString(); @@ -135,10 +135,10 @@ public String debugDump(int indent) { public PrismObject getShadow() { PrismObject shadow; - if (getCurrentShadow() != null) { - shadow = getCurrentShadow(); - } else if (getOldShadow() != null) { - shadow = getOldShadow(); + if (getCurrentResourceObject() != null) { + shadow = getCurrentResourceObject(); + } else if (getOldRepoShadow() != null) { + shadow = getOldRepoShadow(); } else if (getDelta() != null && getDelta().isAdd()) { if (getDelta().getObjectToAdd() == null) { throw new IllegalStateException("Found ADD delta, but no object to add was specified."); diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ProvisioningContext.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ProvisioningContext.java index 54bd5c12a81..6e722f3d668 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ProvisioningContext.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ProvisioningContext.java @@ -19,6 +19,7 @@ import com.evolveum.midpoint.schema.processor.ObjectClassComplexTypeDefinition; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.StateReporter; +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; @@ -57,6 +58,8 @@ public class ProvisioningContext extends StateReporter { private Map,ConnectorInstance> connectorMap; private RefinedResourceSchema refinedSchema; + private String channelOverride; + public ProvisioningContext(@NotNull ResourceManager resourceManager, OperationResult parentResult) { this.resourceManager = resourceManager; this.parentResult = parentResult; @@ -198,7 +201,12 @@ public RefinedObjectClassDefinition computeCompositeObjectClassDefinition(PrismO } public String getChannel() { - return getTask()==null?null:getTask().getChannel(); + Task task = getTask(); + if (task != null && channelOverride == null) { + return task.getChannel(); + } else { + return channelOverride; + } } public ConnectorInstance getConnector(Class operationCapabilityClass, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, ExpressionEvaluationException { @@ -357,4 +365,12 @@ public CachingStategyType getCachingStrategy() ExpressionEvaluationException { return ProvisioningUtil.getCachingStrategy(this); } + + public String getChannelOverride() { + return channelOverride; + } + + public void setChannelOverride(String channelOverride) { + this.channelOverride = channelOverride; + } } diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceEventListenerImpl.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceEventListenerImpl.java index 0433acc1c58..334ee047a6b 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceEventListenerImpl.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceEventListenerImpl.java @@ -72,7 +72,7 @@ public void notifyEvent(ResourceEventDescription eventDescription, Task task, Op LOGGER.trace("Received event notification with the description: {}", eventDescription.debugDumpLazily()); - if (eventDescription.getCurrentShadow() == null && eventDescription.getDelta() == null) { + if (eventDescription.getCurrentResourceObject() == null && eventDescription.getDelta() == null) { throw new IllegalStateException("Neither current shadow, nor delta specified. It is required to have at least one of them specified."); } @@ -84,12 +84,12 @@ public void notifyEvent(ResourceEventDescription eventDescription, Task task, Op Collection> primaryIdentifiers = ShadowUtil.getPrimaryIdentifiers(shadow); - // TODO reconsider this + // TODO reconsider this... MID-5834 (e.g. is this OK with index-only attributes? probably not) if (ctx.getCachingStrategy() == CachingStategyType.PASSIVE) { - if (eventDescription.getCurrentShadow() == null && eventDescription.getOldShadow() != null && eventDescription.getDelta() != null) { - PrismObject newShadow = eventDescription.getOldShadow().clone(); + if (eventDescription.getCurrentResourceObject() == null && eventDescription.getOldRepoShadow() != null && eventDescription.getDelta() != null) { + PrismObject newShadow = eventDescription.getOldRepoShadow().clone(); eventDescription.getDelta().applyTo(newShadow); - eventDescription.setCurrentShadow(newShadow); + eventDescription.setCurrentResourceObject(newShadow); } } @@ -107,8 +107,9 @@ public void notifyEvent(ResourceEventDescription eventDescription, Task task, Op LOGGER.warn("More than one primary identifier real value in {}: {}", eventDescription, primaryIdentifierRealValues); primaryIdentifierRealValue = null; } - Change change = new Change(primaryIdentifierRealValue, primaryIdentifiers, eventDescription.getCurrentShadow(), - eventDescription.getOldShadow(), eventDescription.getDelta()); + Change change = new Change(primaryIdentifierRealValue, primaryIdentifiers, eventDescription.getCurrentResourceObject(), + eventDescription.getDelta()); + change.setOldRepoShadow(eventDescription.getOldRepoShadow()); change.setObjectClassDefinition(ShadowUtil.getObjectClassDefinition(shadow)); LOGGER.trace("Starting to synchronize change: {}", change); @@ -118,12 +119,12 @@ public void notifyEvent(ResourceEventDescription eventDescription, Task task, Op private void applyDefinitions(ResourceEventDescription eventDescription, OperationResult parentResult) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, ExpressionEvaluationException { - if (eventDescription.getCurrentShadow() != null){ - shadowCache.applyDefinition(eventDescription.getCurrentShadow(), parentResult); + if (eventDescription.getCurrentResourceObject() != null){ + shadowCache.applyDefinition(eventDescription.getCurrentResourceObject(), parentResult); } - if (eventDescription.getOldShadow() != null){ - shadowCache.applyDefinition(eventDescription.getOldShadow(), parentResult); + if (eventDescription.getOldRepoShadow() != null){ + shadowCache.applyDefinition(eventDescription.getOldRepoShadow(), parentResult); } if (eventDescription.getDelta() != null) { diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectConverter.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectConverter.java index 076872d42a2..6ab36136bde 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectConverter.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectConverter.java @@ -1823,7 +1823,7 @@ public boolean handleChange(Change change, OperationResult parentResult) { } finally { result.computeStatusIfUnknown(); if (tracingRequested) { - tracer.storeTrace(task, result); + tracer.storeTrace(task, result, parentResult); } } } finally { @@ -1904,14 +1904,14 @@ private boolean preprocessChange(ProvisioningContext ctx, AttributesToReturn att } if (change.getObjectDelta() == null || !change.getObjectDelta().isDelete()) { - if (change.getCurrentShadow() == null) { + if (change.getCurrentResourceObject() == null) { // There is no current shadow in a change. Add it by fetching it explicitly. try { // TODO maybe we can postpone this fetch to ShadowCache.preProcessChange where it is implemented [pmed] LOGGER.trace("Re-fetching object {} because it is not in the change", change.getIdentifiers()); PrismObject currentShadow = fetchResourceObject(shadowCtx, change.getIdentifiers(), shadowAttrsToReturn, true, result); // todo consider whether it is always necessary to fetch the entitlements - change.setCurrentShadow(currentShadow); + change.setCurrentResourceObject(currentShadow); } catch (ObjectNotFoundException ex) { result.recordHandledError( "Object detected in change log no longer exist on the resource. Skipping processing this object.", ex); @@ -1929,12 +1929,12 @@ private boolean preprocessChange(ProvisioningContext ctx, AttributesToReturn att LOGGER.trace("Re-fetching object {} because of attrsToReturn", identification); currentShadow = connector.fetchObject(identification, shadowAttrsToReturn, ctx, result); } else { - currentShadow = change.getCurrentShadow(); + currentShadow = change.getCurrentResourceObject(); } PrismObject processedCurrentShadow = postProcessResourceObjectRead(shadowCtx, currentShadow, true, result); - change.setCurrentShadow(processedCurrentShadow); + change.setCurrentResourceObject(processedCurrentShadow); } } LOGGER.trace("Processed change\n:{}", change.debugDumpLazily()); @@ -1976,11 +1976,11 @@ public String startListeningForAsyncUpdates(@NotNull ProvisioningContext ctx, change.setObjectClassDefinition(shadowCtx.getObjectClassDefinition()); } - if (change.getCurrentShadow() != null) { - shadowCaretaker.applyAttributesDefinition(ctx, change.getCurrentShadow()); + if (change.getCurrentResourceObject() != null) { + shadowCaretaker.applyAttributesDefinition(ctx, change.getCurrentResourceObject()); PrismObject processedCurrentShadow = postProcessResourceObjectRead(shadowCtx, - change.getCurrentShadow(), true, listenerResult); - change.setCurrentShadow(processedCurrentShadow); + change.getCurrentResourceObject(), true, listenerResult); + change.setCurrentResourceObject(processedCurrentShadow); } else { // we will fetch current shadow later } @@ -1998,8 +1998,8 @@ public String startListeningForAsyncUpdates(@NotNull ProvisioningContext ctx, } private void setResourceOidIfMissing(Change change, String resourceOid) { - setResourceOidIfMissing(change.getOldShadow(), resourceOid); - setResourceOidIfMissing(change.getCurrentShadow(), resourceOid); + setResourceOidIfMissing(change.getOldRepoShadow(), resourceOid); + setResourceOidIfMissing(change.getCurrentResourceObject(), resourceOid); if (change.getObjectDelta() != null) { setResourceOidIfMissing(change.getObjectDelta().getObjectToAdd(), resourceOid); } diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectReferenceResolver.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectReferenceResolver.java index 679569c3399..17b0a94a6ed 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectReferenceResolver.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ResourceObjectReferenceResolver.java @@ -11,6 +11,7 @@ import javax.xml.namespace.QName; +import com.evolveum.midpoint.provisioning.impl.shadowmanager.ShadowManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @@ -32,9 +33,7 @@ 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.schema.GetOperationOptions; import com.evolveum.midpoint.schema.ResultHandler; -import com.evolveum.midpoint.schema.SelectorOptions; import com.evolveum.midpoint.schema.processor.ResourceAttribute; import com.evolveum.midpoint.schema.processor.ResourceObjectIdentification; import com.evolveum.midpoint.schema.result.OperationResult; diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCache.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCache.java index e788375c29b..0b3d2215a4c 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCache.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowCache.java @@ -23,6 +23,7 @@ import com.evolveum.midpoint.provisioning.api.*; import com.evolveum.midpoint.provisioning.impl.errorhandling.ErrorHandler; import com.evolveum.midpoint.provisioning.impl.errorhandling.ErrorHandlerLocator; +import com.evolveum.midpoint.provisioning.impl.shadowmanager.ShadowManager; import com.evolveum.midpoint.provisioning.ucf.api.*; import com.evolveum.midpoint.provisioning.util.ProvisioningUtil; import com.evolveum.midpoint.repo.api.RepositoryService; @@ -255,7 +256,6 @@ public PrismObject getShadow(String oid, PrismObject rep Collection> identifiers = ShadowUtil.getAllIdentifiers(repositoryShadow); try { - try { resourceShadow = resourceObjectConverter.getResourceObject(ctx, identifiers, true, parentResult); @@ -292,31 +292,24 @@ public PrismObject getShadow(String oid, PrismObject rep resourceShadow.asObjectable().setIntent(repositoryShadow.asObjectable().getIntent()); ProvisioningContext shadowCtx = ctx.spawn(resourceShadow); - resourceManager.modifyResourceAvailabilityStatus(resource.asPrismObject(), - AvailabilityStatusType.UP, parentResult); + resourceManager.modifyResourceAvailabilityStatus(resource.asPrismObject(), AvailabilityStatusType.UP, parentResult); if (LOGGER.isTraceEnabled()) { LOGGER.trace("Shadow from repository:\n{}", repositoryShadow.debugDump(1)); LOGGER.trace("Resource object fetched from resource:\n{}", resourceShadow.debugDump(1)); } - repositoryShadow = shadowManager.updateShadow(shadowCtx, resourceShadow, repositoryShadow, - shadowState, parentResult); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Repository shadow after update:\n{}", repositoryShadow.debugDump(1)); - } + repositoryShadow = shadowManager.updateShadow(shadowCtx, resourceShadow, null, repositoryShadow, shadowState, parentResult); + LOGGER.trace("Repository shadow after update:\n{}", repositoryShadow.debugDumpLazily(1)); + // Complete the shadow by adding attributes from the resource object // This also completes the associations by adding shadowRefs - PrismObject resultShadow = completeShadow(shadowCtx, resourceShadow, repositoryShadow, false, parentResult); + PrismObject assembledShadow = completeShadow(shadowCtx, resourceShadow, repositoryShadow, false, parentResult); + LOGGER.trace("Shadow when assembled:\n{}", assembledShadow.debugDumpLazily(1)); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Shadow when assembled:\n{}", resultShadow.debugDump(1)); - } + PrismObject resultShadow = futurizeShadow(ctx, repositoryShadow, assembledShadow, options, now); + LOGGER.trace("Futurized assembled shadow:\n{}", resultShadow.debugDumpLazily(1)); - resultShadow = futurizeShadow(ctx, repositoryShadow, resultShadow, options, now); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Futurized assembled shadow:\n{}", resultShadow.debugDump(1)); - } parentResult.recordSuccess(); validateShadow(resultShadow, true); return resultShadow; @@ -333,9 +326,9 @@ public PrismObject getShadow(String oid, PrismObject rep // is returned parentResult.setStatus(OperationResultStatus.PARTIAL_ERROR); } - handledShadow = futurizeShadow(ctx, handledShadow, null, options, now); - validateShadow(handledShadow, true); - return handledShadow; + PrismObject futurizedShadow = futurizeShadow(ctx, handledShadow, null, options, now); + validateShadow(futurizedShadow, true); + return futurizedShadow; } catch (GenericFrameworkException | ObjectAlreadyExistsException | PolicyViolationException e) { throw new SystemException(e.getMessage(), e); @@ -348,7 +341,6 @@ public PrismObject getShadow(String oid, PrismObject rep // fetch for protected objects? if (!ShadowUtil.isProtected(resourceShadow)) { InternalMonitor.recordCount(InternalCounters.SHADOW_FETCH_OPERATION_COUNT); - } } } @@ -2042,10 +2034,13 @@ public SearchResultMetadata searchObjectsIterative(final ProvisioningContext ctx // shadow should have proper kind/intent ProvisioningContext shadowCtx = shadowCaretaker.applyAttributesDefinition(ctx, repoShadow); // TODO: shadowState - repoShadow = shadowManager.updateShadow(shadowCtx, resourceShadow, repoShadow, null, parentResult); + repoShadow = shadowManager.updateShadow(shadowCtx, resourceShadow, null, repoShadow, + null, parentResult); resultShadow = completeShadow(shadowCtx, resourceShadow, repoShadow, isDoDiscovery, objResult); - + + // TODO do we want also to futurize the shadow like in getObject? + //check and fix kind/intent ShadowType repoShadowType = repoShadow.asObjectable(); if (isDoDiscovery && (repoShadowType.getKind() == null || repoShadowType.getIntent() == null)) { //TODO: check also empty? diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/errorhandling/ObjectAlreadyExistHandler.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/errorhandling/ObjectAlreadyExistHandler.java index 0ad45d35cff..24366975a4e 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/errorhandling/ObjectAlreadyExistHandler.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/errorhandling/ObjectAlreadyExistHandler.java @@ -7,7 +7,6 @@ package com.evolveum.midpoint.provisioning.impl.errorhandling; -import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -29,7 +28,7 @@ import com.evolveum.midpoint.provisioning.impl.ProvisioningContext; import com.evolveum.midpoint.provisioning.impl.ProvisioningOperationState; import com.evolveum.midpoint.provisioning.impl.ShadowCaretaker; -import com.evolveum.midpoint.provisioning.impl.ShadowManager; +import com.evolveum.midpoint.provisioning.impl.shadowmanager.ShadowManager; import com.evolveum.midpoint.provisioning.ucf.api.GenericFrameworkException; import com.evolveum.midpoint.provisioning.util.ProvisioningUtil; import com.evolveum.midpoint.repo.api.RepositoryService; diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/errorhandling/ObjectNotFoundHandler.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/errorhandling/ObjectNotFoundHandler.java index f2139bcdd75..42ea1ef4bc9 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/errorhandling/ObjectNotFoundHandler.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/errorhandling/ObjectNotFoundHandler.java @@ -22,7 +22,7 @@ import com.evolveum.midpoint.provisioning.impl.ProvisioningContext; import com.evolveum.midpoint.provisioning.impl.ProvisioningOperationState; import com.evolveum.midpoint.provisioning.impl.ShadowCaretaker; -import com.evolveum.midpoint.provisioning.impl.ShadowManager; +import com.evolveum.midpoint.provisioning.impl.shadowmanager.ShadowManager; import com.evolveum.midpoint.provisioning.impl.ShadowState; import com.evolveum.midpoint.provisioning.ucf.api.GenericFrameworkException; import com.evolveum.midpoint.provisioning.util.ProvisioningUtil; diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/shadowmanager/ShadowDeltaComputer.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/shadowmanager/ShadowDeltaComputer.java new file mode 100644 index 00000000000..38e4709f49a --- /dev/null +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/shadowmanager/ShadowDeltaComputer.java @@ -0,0 +1,282 @@ +/* + * Copyright (c) 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.provisioning.impl.shadowmanager; + +import com.evolveum.midpoint.common.Clock; +import com.evolveum.midpoint.common.refinery.RefinedAttributeDefinition; +import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; +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.PropertyDelta; +import com.evolveum.midpoint.prism.match.MatchingRule; +import com.evolveum.midpoint.prism.match.MatchingRuleRegistry; +import com.evolveum.midpoint.prism.path.ItemPath; +import com.evolveum.midpoint.prism.polystring.PolyString; +import com.evolveum.midpoint.provisioning.impl.ProvisioningContext; +import com.evolveum.midpoint.provisioning.impl.ShadowState; +import com.evolveum.midpoint.provisioning.util.ProvisioningUtil; +import com.evolveum.midpoint.schema.constants.SchemaConstants; +import com.evolveum.midpoint.schema.util.ShadowUtil; +import com.evolveum.midpoint.util.QNameUtil; +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.CachingMetadataType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CachingStategyType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.xml.namespace.QName; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; + +/** + * Computes deltas to be applied to repository shadows. + * This functionality grew too large to deserve special implementation class. + * + * In the future we might move more functionality here and rename this class. + */ +@Component +public class ShadowDeltaComputer { + + private static final Trace LOGGER = TraceManager.getTrace(ShadowDeltaComputer.class); + + @Autowired private Clock clock; + @Autowired private MatchingRuleRegistry matchingRuleRegistry; + @Autowired private PrismContext prismContext; + + @NotNull + ObjectDelta computeShadowDelta(@NotNull ProvisioningContext ctx, + @NotNull PrismObject repoShadowOld, PrismObject resourceShadowNew, + ObjectDelta resourceObjectDelta, ShadowState shadowState) + throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, + ExpressionEvaluationException { + + ObjectDelta computedShadowDelta = repoShadowOld.createModifyDelta(); + + RefinedObjectClassDefinition ocDef = ctx.computeCompositeObjectClassDefinition(resourceShadowNew); + PrismContainer currentResourceAttributes = resourceShadowNew.findContainer(ShadowType.F_ATTRIBUTES); + PrismContainer oldRepoAttributes = repoShadowOld.findContainer(ShadowType.F_ATTRIBUTES); + ShadowType oldRepoShadowType = repoShadowOld.asObjectable(); + + CachingStategyType cachingStrategy = ProvisioningUtil.getCachingStrategy(ctx); + Collection incompleteCacheableItems = new HashSet<>(); + + processAttributes(computedShadowDelta, incompleteCacheableItems, oldRepoAttributes, currentResourceAttributes, + resourceObjectDelta, ocDef, cachingStrategy); + + PolyString currentShadowName = ShadowUtil.determineShadowName(resourceShadowNew); + PolyString oldRepoShadowName = repoShadowOld.getName(); + if (!currentShadowName.equalsOriginalValue(oldRepoShadowName)) { + PropertyDelta shadowNameDelta = prismContext.deltaFactory().property().createModificationReplaceProperty(ShadowType.F_NAME, + repoShadowOld.getDefinition(),currentShadowName); + computedShadowDelta.addModification(shadowNameDelta); + } + + PropertyDelta auxOcDelta = ItemUtil.diff( + repoShadowOld.findProperty(ShadowType.F_AUXILIARY_OBJECT_CLASS), + resourceShadowNew.findProperty(ShadowType.F_AUXILIARY_OBJECT_CLASS)); + if (auxOcDelta != null) { + computedShadowDelta.addModification(auxOcDelta); + } + + // Resource object obviously exists in this case. However, we do not want to mess with isExists flag in some + // situations (e.g. in CORPSE state) as this existence may be just a quantum illusion. + if (shadowState == ShadowState.CONCEPTION || shadowState == ShadowState.GESTATION) { + PropertyDelta existsDelta = computedShadowDelta.createPropertyModification(ShadowType.F_EXISTS); + existsDelta.setRealValuesToReplace(true); + computedShadowDelta.addModification(existsDelta); + } + + if (cachingStrategy == CachingStategyType.NONE) { + if (oldRepoShadowType.getCachingMetadata() != null) { + computedShadowDelta.addModificationReplaceProperty(ShadowType.F_CACHING_METADATA); + } + + } else if (cachingStrategy == CachingStategyType.PASSIVE) { + + compareUpdateProperty(computedShadowDelta, SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, resourceShadowNew, repoShadowOld); + compareUpdateProperty(computedShadowDelta, SchemaConstants.PATH_ACTIVATION_VALID_FROM, resourceShadowNew, repoShadowOld); + compareUpdateProperty(computedShadowDelta, SchemaConstants.PATH_ACTIVATION_VALID_TO, resourceShadowNew, repoShadowOld); + compareUpdateProperty(computedShadowDelta, SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS, resourceShadowNew, repoShadowOld); + + if (incompleteCacheableItems.isEmpty()) { + CachingMetadataType cachingMetadata = new CachingMetadataType(); + cachingMetadata.setRetrievalTimestamp(clock.currentTimeXMLGregorianCalendar()); + computedShadowDelta.addModificationReplaceProperty(ShadowType.F_CACHING_METADATA, cachingMetadata); + } else { + LOGGER.trace("Shadow has incomplete cacheable items; will not update caching timestamp: {}", incompleteCacheableItems); + } + } else { + throw new ConfigurationException("Unknown caching strategy "+cachingStrategy); + } + return computedShadowDelta; + } + + private void processAttributes(ObjectDelta computedShadowDelta, Collection incompleteCacheableAttributes, + PrismContainer oldRepoAttributes, PrismContainer currentResourceAttributes, + ObjectDelta resourceObjectDelta, RefinedObjectClassDefinition ocDef, CachingStategyType cachingStrategy) + throws SchemaException, ConfigurationException { + + // For complete attributes we can proceed as before: take currentResourceAttributes as authoritative. + // If not obtained from the resource, they were created from object delta anyway. :) + // However, for incomplete (e.g. index-only) attributes we have to rely on object delta, if present. + // TODO clean this up! MID-5834 + + for (Item currentResourceAttrItem: currentResourceAttributes.getValue().getItems()) { + if (currentResourceAttrItem instanceof PrismProperty) { + //noinspection unchecked + PrismProperty currentResourceAttrProperty = (PrismProperty) currentResourceAttrItem; + RefinedAttributeDefinition attrDef = ocDef.findAttributeDefinition(currentResourceAttrProperty.getElementName()); + if (attrDef == null) { + throw new SchemaException("No definition of " + currentResourceAttrProperty.getElementName() + " in " + ocDef); + } + if (ProvisioningUtil.shouldStoreAttributeInShadow(ocDef, attrDef.getItemName(), cachingStrategy)) { + if (!currentResourceAttrItem.isIncomplete()) { + processResourceAttribute(computedShadowDelta, oldRepoAttributes, currentResourceAttrProperty, attrDef); + } else { + incompleteCacheableAttributes.add(attrDef.getItemName()); + if (resourceObjectDelta != null) { + LOGGER.trace( + "Resource attribute {} is incomplete but a delta does exist: we'll update the shadow " + + "using the delta", attrDef.getItemName()); + } else { + LOGGER.trace( + "Resource attribute {} is incomplete and object delta is not present: will not update the" + + " shadow with its content", attrDef.getItemName()); + } + } + } else { + LOGGER.trace("Skipping resource attribute because it's not going to be stored in shadow: {}", + attrDef.getItemName()); + } + } else { + LOGGER.warn("Skipping resource attribute because it's not a PrismProperty (huh?): {}", currentResourceAttrItem); + } + } + + for (Item oldRepoItem: oldRepoAttributes.getValue().getItems()) { + if (oldRepoItem instanceof PrismProperty) { + PrismProperty oldRepoAttrProperty = (PrismProperty) oldRepoItem; + RefinedAttributeDefinition attrDef = ocDef.findAttributeDefinition(oldRepoAttrProperty.getElementName()); + PrismProperty currentAttribute = currentResourceAttributes.findProperty(oldRepoAttrProperty.getElementName()); + // note: incomplete attributes with no values are not here: they are found in currentResourceAttributes container + if (attrDef == null || !ProvisioningUtil.shouldStoreAttributeInShadow(ocDef, attrDef.getItemName(), cachingStrategy) || + currentAttribute == null) { + // No definition for this property it should not be there or no current value: remove it from the shadow + PropertyDelta oldRepoAttrPropDelta = oldRepoAttrProperty.createDelta(); + if (oldRepoAttrPropDelta.getDefinition().getTypeName() == null) { + throw new SchemaException("No definition in "+oldRepoAttrPropDelta); + } + //noinspection unchecked + oldRepoAttrPropDelta.addValuesToDelete((Collection) PrismValueCollectionsUtil.cloneCollection(oldRepoAttrProperty.getValues())); + computedShadowDelta.addModification(oldRepoAttrPropDelta); + } + } else { + LOGGER.warn("Skipping repo shadow attribute because it's not a PrismProperty (huh?): {}", oldRepoItem); + } + } + + if (resourceObjectDelta != null && !incompleteCacheableAttributes.isEmpty()) { + LOGGER.trace("Found incomplete cacheable attributes: {} while resource object delta is known. " + + "We'll update them using the delta.", incompleteCacheableAttributes); + for (ItemDelta modification : resourceObjectDelta.getModifications()) { + if (modification.getPath().startsWith(ShadowType.F_ATTRIBUTES)) { + if (QNameUtil.contains(incompleteCacheableAttributes, modification.getElementName())) { + LOGGER.trace(" - using: {}", modification); + computedShadowDelta.addModification(modification.clone()); + } + } + } + incompleteCacheableAttributes.clear(); // So we are OK regarding this. We can update caching timestamp. + } + } + + private void processResourceAttribute(ObjectDelta computedShadowDelta, + PrismContainer oldRepoAttributes, PrismProperty currentResourceAttrProperty, + RefinedAttributeDefinition attrDef) + throws SchemaException { + MatchingRule matchingRule = matchingRuleRegistry.getMatchingRule(attrDef.getMatchingRuleQName(), attrDef.getTypeName()); + PrismProperty oldRepoAttributeProperty = oldRepoAttributes.findProperty(attrDef.getItemName()); + if (oldRepoAttributeProperty == null) { + PropertyDelta attrAddDelta = currentResourceAttrProperty.createDelta(); + List> currentValues = currentResourceAttrProperty.getValues(); + // This is a brutal hack: For extension attributes the ADD operation is slow when using large # of + // values to add. So let's do REPLACE instead (this is OK if there are no existing values). + // TODO Move this logic to repository. Here it is only for PoC purposes. + if (currentValues.size() >= 100) { + Object[] currentValuesNormalized = new Object[currentValues.size()]; + for (int i = 0; i < currentValues.size(); i++) { + currentValuesNormalized[i] = matchingRule.normalize(currentValues.get(i).getValue()); + } + attrAddDelta.setRealValuesToReplace(currentValuesNormalized); + } else { + for (PrismPropertyValue pval : currentValues) { + attrAddDelta.addRealValuesToAdd(matchingRule.normalize(pval.getValue())); + } + } + if (attrAddDelta.getDefinition().getTypeName() == null) { + throw new SchemaException("No definition in " + attrAddDelta); + } + computedShadowDelta.addModification(attrAddDelta); + } else { + if (attrDef.isSingleValue()) { + Object currentResourceRealValue = currentResourceAttrProperty.getRealValue(); + Object currentResourceNormalizedRealValue = matchingRule.normalize(currentResourceRealValue); + if (!Objects.equals(currentResourceNormalizedRealValue, oldRepoAttributeProperty.getRealValue())) { + PropertyDelta delta; + if (currentResourceNormalizedRealValue != null) { + delta = computedShadowDelta.addModificationReplaceProperty(currentResourceAttrProperty.getPath(), + currentResourceNormalizedRealValue); + } else { + delta = computedShadowDelta.addModificationReplaceProperty(currentResourceAttrProperty.getPath()); + } + //noinspection unchecked + delta.setDefinition(currentResourceAttrProperty.getDefinition()); + if (delta.getDefinition().getTypeName() == null) { + throw new SchemaException("No definition in " + delta); + } + } + } else { + PrismProperty normalizedCurrentResourceAttrProperty = currentResourceAttrProperty.clone(); + for (PrismPropertyValue pval : normalizedCurrentResourceAttrProperty.getValues()) { + Object normalizedRealValue = matchingRule.normalize(pval.getValue()); + //noinspection unchecked + pval.setValue(normalizedRealValue); + } + PropertyDelta attrDiff = oldRepoAttributeProperty.diff(normalizedCurrentResourceAttrProperty); + // LOGGER.trace("DIFF:\n{}\n-\n{}\n=:\n{}", + // oldRepoAttributeProperty==null?null:oldRepoAttributeProperty.debugDump(1), + // normalizedCurrentResourceAttrProperty==null?null:normalizedCurrentResourceAttrProperty.debugDump(1), + // attrDiff==null?null:attrDiff.debugDump(1)); + if (attrDiff != null && !attrDiff.isEmpty()) { + attrDiff.setParentPath(ShadowType.F_ATTRIBUTES); + if (attrDiff.getDefinition().getTypeName() == null) { + throw new SchemaException("No definition in " + attrDiff); + } + computedShadowDelta.addModification(attrDiff); + } + } + } + } + + private void compareUpdateProperty(ObjectDelta shadowDelta, + ItemPath itemPath, PrismObject currentResourceShadow, PrismObject oldRepoShadow) { + PrismProperty currentProperty = currentResourceShadow.findProperty(itemPath); + PrismProperty oldProperty = oldRepoShadow.findProperty(itemPath); + PropertyDelta itemDelta = ItemUtil.diff(oldProperty, currentProperty); + if (itemDelta != null && !itemDelta.isEmpty()) { + shadowDelta.addModification(itemDelta); + } + } +} diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowManager.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/shadowmanager/ShadowManager.java similarity index 90% rename from provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowManager.java rename to provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/shadowmanager/ShadowManager.java index a5438c9f778..c6997f6d02e 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ShadowManager.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/shadowmanager/ShadowManager.java @@ -5,7 +5,7 @@ * and European Union Public License. See LICENSE file for details. */ -package com.evolveum.midpoint.provisioning.impl; +package com.evolveum.midpoint.provisioning.impl.shadowmanager; import java.util.*; import java.util.Objects; @@ -18,6 +18,10 @@ import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.path.ItemName; import com.evolveum.midpoint.provisioning.api.ProvisioningService; +import com.evolveum.midpoint.provisioning.impl.ConstraintsChecker; +import com.evolveum.midpoint.provisioning.impl.ProvisioningContext; +import com.evolveum.midpoint.provisioning.impl.ProvisioningOperationState; +import com.evolveum.midpoint.provisioning.impl.ShadowState; import com.evolveum.midpoint.schema.*; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.apache.commons.lang.BooleanUtils; @@ -103,6 +107,7 @@ public class ShadowManager { @Autowired private MatchingRuleRegistry matchingRuleRegistry; @Autowired private Protector protector; @Autowired private ProvisioningService provisioningService; + @Autowired private ShadowDeltaComputer shadowDeltaComputer; private static final Trace LOGGER = TraceManager.getTrace(ShadowManager.class); @@ -198,7 +203,7 @@ public PrismObject lookupShadowByPrimaryIdentifierValue(Provisioning } if (foundShadows.size() > 1) { // This cannot happen, there is an unique constraint on primaryIdentifierValue - LOGGER.error("Impossible just happened, found {} shadows for primaryIdentifierValue {}: {}", foundShadows.size(), primaryIdentifierValue); + LOGGER.error("Impossible just happened, found {} shadows for primaryIdentifierValue {}: {}", foundShadows.size(), primaryIdentifierValue, foundShadows); throw new SystemException("Impossible just happened, found "+foundShadows.size()+" shadows for primaryIdentifierValue "+primaryIdentifierValue); } @@ -480,7 +485,7 @@ private PrismObject createNewShadowFromChange(ProvisioningContext ct assert !change.isDelete(); - PrismObject shadow = change.getCurrentShadow(); + PrismObject shadow = change.getCurrentResourceObject(); if (shadow == null) { if (change.isAdd()) { @@ -701,7 +706,7 @@ public PrismObject addDiscoveredRepositoryShadow(ProvisioningContext } public void addNewProposedShadow(ProvisioningContext ctx, PrismObject shadowToAdd, - ProvisioningOperationState>> opState, + ProvisioningOperationState>> opState, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, ExpressionEvaluationException, ObjectAlreadyExistsException, SecurityViolationException, EncryptionException { if (!isUseProposedShadows(ctx)) { return; @@ -814,9 +819,6 @@ private void recordAddResultNewShadow( parentResult.recordSuccess(); } - - - @SuppressWarnings("unchecked") private void recordAddResultExistingShadow( ProvisioningContext ctx, PrismObject shadowToAdd, @@ -1769,7 +1771,7 @@ private void hashValues(Collection> pval } } - @SuppressWarnings("unchecked") + // TODO remove this method if really not needed public Collection updateShadow(ProvisioningContext ctx, PrismObject resourceShadow, Collection aprioriDeltas, OperationResult result) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, ExpressionEvaluationException { PrismObject repoShadow = repositoryService.getObject(ShadowType.class, resourceShadow.getOid(), null, result); @@ -1778,9 +1780,7 @@ public Collection updateShadow(ProvisioningContext ctx, PrismObject updateShadow(@NotNull ProvisioningContext ctx, - @NotNull PrismObject resourceShadowNew, - @NotNull PrismObject repoShadowOld, ShadowState shadowState, OperationResult parentResult) throws SchemaException, - ObjectNotFoundException, ConfigurationException, CommunicationException, ExpressionEvaluationException { + @NotNull PrismObject currentResourceObject, ObjectDelta resourceObjectDelta, + @NotNull PrismObject oldShadow, ShadowState shadowState, OperationResult parentResult) + throws SchemaException, ObjectNotFoundException, ConfigurationException, CommunicationException, + ExpressionEvaluationException { - ObjectDelta shadowDelta = computeShadowDelta(ctx, repoShadowOld, resourceShadowNew, shadowState); + ObjectDelta computedShadowDelta = shadowDeltaComputer.computeShadowDelta(ctx, oldShadow, currentResourceObject, + resourceObjectDelta, shadowState); - if (!shadowDelta.isEmpty()) { - LOGGER.trace("Updating repo shadow {} with delta:\n{}", repoShadowOld, shadowDelta.debugDumpLazily(1)); - ConstraintsChecker.onShadowModifyOperation(shadowDelta.getModifications()); + if (!computedShadowDelta.isEmpty()) { + LOGGER.trace("Updating repo shadow {} with delta:\n{}", oldShadow, computedShadowDelta.debugDumpLazily(1)); + ConstraintsChecker.onShadowModifyOperation(computedShadowDelta.getModifications()); try { - repositoryService.modifyObject(ShadowType.class, repoShadowOld.getOid(), shadowDelta.getModifications(), parentResult); + repositoryService.modifyObject(ShadowType.class, oldShadow.getOid(), computedShadowDelta.getModifications(), parentResult); } catch (ObjectAlreadyExistsException e) { throw new SystemException(e.getMessage(), e); // This should not happen for shadows } - PrismObject repoShadowNew = repoShadowOld.clone(); - shadowDelta.applyTo(repoShadowNew); - return repoShadowNew; + PrismObject newShadow = oldShadow.clone(); + computedShadowDelta.applyTo(newShadow); + return newShadow; } else { - LOGGER.trace("No need to update repo shadow {} (empty delta)", repoShadowOld); - return repoShadowOld; + LOGGER.trace("No need to update repo shadow {} (empty delta)", oldShadow); + return oldShadow; } } - private ObjectDelta computeShadowDelta(@NotNull ProvisioningContext ctx, - @NotNull PrismObject repoShadowOld, - PrismObject resourceShadowNew, ShadowState shadowState) - throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, - ExpressionEvaluationException { - RefinedObjectClassDefinition ocDef = ctx.computeCompositeObjectClassDefinition(resourceShadowNew); - ObjectDelta shadowDelta = repoShadowOld.createModifyDelta(); - PrismContainer currentResourceAttributesContainer = resourceShadowNew.findContainer(ShadowType.F_ATTRIBUTES); - PrismContainer oldRepoAttributesContainer = repoShadowOld.findContainer(ShadowType.F_ATTRIBUTES); - ShadowType oldRepoShadowType = repoShadowOld.asObjectable(); - - CachingStategyType cachingStrategy = ProvisioningUtil.getCachingStrategy(ctx); - - Collection incompleteCacheableItems = new HashSet<>(); - - for (Item currentResourceItem: currentResourceAttributesContainer.getValue().getItems()) { - if (currentResourceItem instanceof PrismProperty) { - //noinspection unchecked - PrismProperty currentResourceAttrProperty = (PrismProperty) currentResourceItem; - RefinedAttributeDefinition attrDef = ocDef.findAttributeDefinition(currentResourceAttrProperty.getElementName()); - if (attrDef == null) { - throw new SchemaException("No definition of " + currentResourceAttrProperty.getElementName() + " in " + ocDef); - } - if (ProvisioningUtil.shouldStoreAttributeInShadow(ocDef, attrDef.getItemName(), cachingStrategy)) { - if (!currentResourceItem.isIncomplete()) { - MatchingRule matchingRule = matchingRuleRegistry.getMatchingRule(attrDef.getMatchingRuleQName(), attrDef.getTypeName()); - PrismProperty oldRepoAttributeProperty = oldRepoAttributesContainer.findProperty(currentResourceAttrProperty.getElementName()); - if (oldRepoAttributeProperty == null) { - PropertyDelta attrAddDelta = currentResourceAttrProperty.createDelta(); - List> currentValues = currentResourceAttrProperty.getValues(); - // This is a brutal hack: For extension attributes the ADD operation is slow when using large # of - // values to add. So let's do REPLACE instead (this is OK if there are no existing values). - // TODO Move this logic to repository. Here it is only for PoC purposes. - if (currentValues.size() >= 100) { - Object[] currentValuesNormalized = new Object[currentValues.size()]; - for (int i = 0; i < currentValues.size(); i++) { - currentValuesNormalized[i] = matchingRule.normalize(currentValues.get(i).getValue()); - } - attrAddDelta.setRealValuesToReplace(currentValuesNormalized); - } else { - for (PrismPropertyValue pval : currentValues) { - attrAddDelta.addRealValuesToAdd(matchingRule.normalize(pval.getValue())); - } - } - if (attrAddDelta.getDefinition().getTypeName() == null) { - throw new SchemaException("No definition in " + attrAddDelta); - } - shadowDelta.addModification(attrAddDelta); - } else { - if (attrDef.isSingleValue()) { - Object currentResourceRealValue = currentResourceAttrProperty.getRealValue(); - Object currentResourceNormalizedRealValue = matchingRule.normalize(currentResourceRealValue); - if (!Objects.equals(currentResourceNormalizedRealValue, oldRepoAttributeProperty.getRealValue())) { - PropertyDelta delta; - if (currentResourceNormalizedRealValue != null) { - delta = shadowDelta.addModificationReplaceProperty(currentResourceAttrProperty.getPath(), - currentResourceNormalizedRealValue); - } else { - delta = shadowDelta.addModificationReplaceProperty(currentResourceAttrProperty.getPath()); - } - //noinspection unchecked - delta.setDefinition(currentResourceAttrProperty.getDefinition()); - if (delta.getDefinition().getTypeName() == null) { - throw new SchemaException("No definition in " + delta); - } - } - } else { - PrismProperty normalizedCurrentResourceAttrProperty = currentResourceAttrProperty.clone(); - for (PrismPropertyValue pval : normalizedCurrentResourceAttrProperty.getValues()) { - Object normalizedRealValue = matchingRule.normalize(pval.getValue()); - //noinspection unchecked - pval.setValue(normalizedRealValue); - } - PropertyDelta attrDiff = oldRepoAttributeProperty.diff(normalizedCurrentResourceAttrProperty); -// LOGGER.trace("DIFF:\n{}\n-\n{}\n=:\n{}", -// oldRepoAttributeProperty==null?null:oldRepoAttributeProperty.debugDump(1), -// normalizedCurrentResourceAttrProperty==null?null:normalizedCurrentResourceAttrProperty.debugDump(1), -// attrDiff==null?null:attrDiff.debugDump(1)); - if (attrDiff != null && !attrDiff.isEmpty()) { - attrDiff.setParentPath(ShadowType.F_ATTRIBUTES); - if (attrDiff.getDefinition().getTypeName() == null) { - throw new SchemaException("No definition in " + attrDiff); - } - shadowDelta.addModification(attrDiff); - } - } - } - } else { - LOGGER.trace("Resource item {} is incomplete, will not update the shadow with its content", - currentResourceItem.getElementName()); - incompleteCacheableItems.add(currentResourceItem.getElementName()); - } - } - } - } - - for (Item oldRepoItem: oldRepoAttributesContainer.getValue().getItems()) { - if (oldRepoItem instanceof PrismProperty) { - PrismProperty oldRepoAttrProperty = (PrismProperty)oldRepoItem; - RefinedAttributeDefinition attrDef = ocDef.findAttributeDefinition(oldRepoAttrProperty.getElementName()); - PrismProperty currentAttribute = currentResourceAttributesContainer.findProperty(oldRepoAttrProperty.getElementName()); - // note: incomplete attributes with no values are not here: they are found in currentResourceAttributesContainer - if (attrDef == null || !ProvisioningUtil.shouldStoreAttributeInShadow(ocDef, attrDef.getItemName(), cachingStrategy) || - currentAttribute == null) { - // No definition for this property it should not be there or no current value: remove it from the shadow - PropertyDelta oldRepoAttrPropDelta = oldRepoAttrProperty.createDelta(); - oldRepoAttrPropDelta.addValuesToDelete((Collection) PrismValueCollectionsUtil.cloneCollection(oldRepoAttrProperty.getValues())); - if (oldRepoAttrPropDelta.getDefinition().getTypeName() == null) { - throw new SchemaException("No definition in "+oldRepoAttrPropDelta); - } - shadowDelta.addModification(oldRepoAttrPropDelta); - } - } - } - - PolyString currentShadowName = ShadowUtil.determineShadowName(resourceShadowNew); - PolyString oldRepoShadowName = repoShadowOld.getName(); - if (!currentShadowName.equalsOriginalValue(oldRepoShadowName)) { - PropertyDelta shadowNameDelta = prismContext.deltaFactory().property().createModificationReplaceProperty(ShadowType.F_NAME, - repoShadowOld.getDefinition(),currentShadowName); - shadowDelta.addModification(shadowNameDelta); - } - - PropertyDelta auxOcDelta = ItemUtil.diff( - repoShadowOld.findProperty(ShadowType.F_AUXILIARY_OBJECT_CLASS), - resourceShadowNew.findProperty(ShadowType.F_AUXILIARY_OBJECT_CLASS)); - if (auxOcDelta != null) { - shadowDelta.addModification(auxOcDelta); - } - - // Resource object obviously exists in this case. However, we do not want to mess with isExists flag in some - // situations (e.g. in CORPSE state) as this existence may be just a quantum illusion. - if (shadowState == ShadowState.CONCEPTION || shadowState == ShadowState.GESTATION) { - PropertyDelta existsDelta = shadowDelta.createPropertyModification(ShadowType.F_EXISTS); - existsDelta.setRealValuesToReplace(true); - shadowDelta.addModification(existsDelta); - } - - if (cachingStrategy == CachingStategyType.NONE) { - if (oldRepoShadowType.getCachingMetadata() != null) { - shadowDelta.addModificationReplaceProperty(ShadowType.F_CACHING_METADATA); - } - - } else if (cachingStrategy == CachingStategyType.PASSIVE) { - - compareUpdateProperty(shadowDelta, SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, resourceShadowNew, repoShadowOld); - compareUpdateProperty(shadowDelta, SchemaConstants.PATH_ACTIVATION_VALID_FROM, resourceShadowNew, repoShadowOld); - compareUpdateProperty(shadowDelta, SchemaConstants.PATH_ACTIVATION_VALID_TO, resourceShadowNew, repoShadowOld); - compareUpdateProperty(shadowDelta, SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS, resourceShadowNew, repoShadowOld); - - if (incompleteCacheableItems.isEmpty()) { - CachingMetadataType cachingMetadata = new CachingMetadataType(); - cachingMetadata.setRetrievalTimestamp(clock.currentTimeXMLGregorianCalendar()); - shadowDelta.addModificationReplaceProperty(ShadowType.F_CACHING_METADATA, cachingMetadata); - } else { - LOGGER.trace("Shadow has incomplete cacheable items; will not update caching timestamp: {}", incompleteCacheableItems); - } - } else { - throw new ConfigurationException("Unknown caching strategy "+cachingStrategy); - } - return shadowDelta; - } - - private void compareUpdateProperty(ObjectDelta shadowDelta, - ItemPath itemPath, PrismObject currentResourceShadow, PrismObject oldRepoShadow) { - PrismProperty currentProperty = currentResourceShadow.findProperty(itemPath); - PrismProperty oldProperty = oldRepoShadow.findProperty(itemPath); - PropertyDelta itemDelta = ItemUtil.diff(oldProperty, currentProperty); - if (itemDelta != null && !itemDelta.isEmpty()) { - shadowDelta.addModification(itemDelta); - } - } - public PrismObject recordDeleteResult( ProvisioningContext ctx, PrismObject oldRepoShadow, diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/sync/AsyncUpdater.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/sync/AsyncUpdater.java index 2fd8b174190..cd2f4bb91d1 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/sync/AsyncUpdater.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/sync/AsyncUpdater.java @@ -45,6 +45,7 @@ public String startListeningForAsyncUpdates(ResourceShadowDiscriminator shadowCo InternalMonitor.recordCount(InternalCounters.PROVISIONING_ALL_EXT_OPERATION_COUNT); ProvisioningContext ctx = ctxFactory.create(shadowCoordinates, callerTask, callerResult); + ChangeListener listener = (change, listenerTask, listenerResult) -> { ProcessChangeRequest request = new ProcessChangeRequest(change, ctx, false); changeProcessor.execute(request, listenerTask, null, listenerResult); diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/sync/ChangeProcessor.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/sync/ChangeProcessor.java index 1160f0444cf..a33de3b1faf 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/sync/ChangeProcessor.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/sync/ChangeProcessor.java @@ -7,15 +7,20 @@ package com.evolveum.midpoint.provisioning.impl.sync; +import com.evolveum.midpoint.common.refinery.RefinedAttributeDefinition; +import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; +import com.evolveum.midpoint.prism.Item; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.crypto.EncryptionException; +import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.match.MatchingRuleRegistry; +import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.provisioning.api.GenericConnectorException; import com.evolveum.midpoint.provisioning.api.ResourceObjectShadowChangeDescription; import com.evolveum.midpoint.provisioning.impl.ProvisioningContext; import com.evolveum.midpoint.provisioning.impl.ShadowCache; import com.evolveum.midpoint.provisioning.impl.ShadowCaretaker; -import com.evolveum.midpoint.provisioning.impl.ShadowManager; +import com.evolveum.midpoint.provisioning.impl.shadowmanager.ShadowManager; import com.evolveum.midpoint.provisioning.ucf.api.Change; import com.evolveum.midpoint.provisioning.util.ProvisioningUtil; import com.evolveum.midpoint.repo.api.PreconditionViolationException; @@ -80,21 +85,30 @@ public void execute(ProcessChangeRequest request, Task task, TaskPartitionDefini return; } + // This is a bit of hack to propagate information about async update channel to upper layers + // e.g. to implement MID-5853. In async update scenario the task here is the newly-created + // that is used to execute the request. On the other hand, the task in globalCtx is the original + // one that was used to start listening for changes. TODO This is to be cleaned up. + // But for the time being let's forward with this hack. + if (SchemaConstants.CHANGE_CHANNEL_ASYNC_UPDATE_URI.equals(task.getChannel())) { + ctx.setChannelOverride(SchemaConstants.CHANGE_CHANNEL_ASYNC_UPDATE_URI); + } + if (change.getObjectDelta() != null) { shadowCaretaker.applyAttributesDefinition(ctx, change.getObjectDelta()); } - if (change.getOldShadow() != null) { - shadowCaretaker.applyAttributesDefinition(ctx, change.getOldShadow()); + if (change.getOldRepoShadow() != null) { + shadowCaretaker.applyAttributesDefinition(ctx, change.getOldRepoShadow()); } - if (change.getCurrentShadow() != null) { - shadowCaretaker.applyAttributesDefinition(ctx, change.getCurrentShadow()); // maybe redundant + if (change.getCurrentResourceObject() != null) { + shadowCaretaker.applyAttributesDefinition(ctx, change.getCurrentResourceObject()); // maybe redundant } preProcessChange(ctx, change, result); // This is the case when we want to skip processing of change because the shadow was not found nor created. // The usual reason is that this is the delete delta and the object was already deleted on both resource and in repo. - if (change.getOldShadow() == null) { + if (change.getOldRepoShadow() == null) { assert change.isDelete(); LOGGER.debug("Skipping processing change. Can't find appropriate shadow (e.g. the object was " + "deleted on the resource meantime)."); @@ -181,17 +195,17 @@ private ProvisioningContext determineProvisioningContext(ProvisioningContext glo } else { // A specialty for DELETE changes: we try to determine the object class if not specified in the change // It is needed e.g. for some LDAP servers doing live synchronization. - if (change.getOldShadow() == null) { + if (change.getOldRepoShadow() == null) { PrismObject existing = shadowManager.findOrAddShadowFromChange(globalCtx, change, parentResult); if (existing == null) { LOGGER.debug("No old shadow for delete synchronization event {}, we probably did not know about " + "that object anyway, so well be ignoring this event", change); return null; } else { - change.setOldShadow(existing); + change.setOldRepoShadow(existing); } } - return globalCtx.spawn(change.getOldShadow()); + return globalCtx.spawn(change.getOldRepoShadow()); } } else { return globalCtx.spawn(change.getObjectClassDefinition().getTypeName()); @@ -208,11 +222,11 @@ private void preProcessChange(ProvisioningContext ctx, Change change, OperationR throws SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, ObjectNotFoundException, GenericConnectorException, ExpressionEvaluationException, EncryptionException { - if (change.getOldShadow() == null) { + if (change.getOldRepoShadow() == null) { PrismObject repoShadow = shadowManager.findOrAddShadowFromChange(ctx, change, parentResult); if (repoShadow != null) { shadowCaretaker.applyAttributesDefinition(ctx, repoShadow); - change.setOldShadow(repoShadow); + change.setOldRepoShadow(repoShadow); } else { assert change.isDelete(); LOGGER.debug("No old shadow for synchronization event {}, the shadow must be gone in the meantime " @@ -221,46 +235,54 @@ private void preProcessChange(ProvisioningContext ctx, Change change, OperationR } } - @NotNull PrismObject oldShadow = change.getOldShadow(); + @NotNull PrismObject oldShadow = change.getOldRepoShadow(); LOGGER.trace("Processing change, old shadow: {}", ShadowUtil.shortDumpShadow(oldShadow)); ProvisioningUtil.setProtectedFlag(ctx, oldShadow, matchingRuleRegistry, relationRegistry); - if (change.getCurrentShadow() == null && !change.isDelete()) { - // Temporary measure: let us determine the current shadow; either by fetching it from the resource + if (change.getCurrentResourceObject() == null && !change.isDelete()) { + LOGGER.trace("Going to compute current resource object because it's null and delta is not delete"); + // Temporary measure: let us determine the current resource object; either by fetching it from the resource // (if possible) or by taking cached values and applying the delta. In the future we might implement // pure delta changes that do not need to know the current state. if (change.isAdd()) { - change.setCurrentShadow(change.getObjectDelta().getObjectToAdd().clone()); + change.setCurrentResourceObject(change.getObjectDelta().getObjectToAdd().clone()); + LOGGER.trace("...taken from ADD delta:\n{}", change.getCurrentResourceObject().debugDumpLazily()); } else { if (ctx.getCachingStrategy() == CachingStategyType.PASSIVE) { - PrismObject currentShadow = oldShadow.clone(); + PrismObject resourceObject = oldShadow.clone(); // this might not be correct w.r.t. index-only attributes! if (change.getObjectDelta() != null) { - change.getObjectDelta().applyTo(currentShadow); + change.getObjectDelta().applyTo(resourceObject); + markIndexOnlyItemsAsIncomplete(resourceObject, change.getObjectDelta(), ctx); + LOGGER.trace("...taken from old shadow + delta:\n{}", resourceObject.debugDumpLazily()); + } else { + LOGGER.trace("...taken from old shadow:\n{}", resourceObject.debugDumpLazily()); } - change.setCurrentShadow(currentShadow); + change.setCurrentResourceObject(resourceObject); } else { // no caching; let us retrieve the object from the resource Collection> options = schemaHelper.getOperationOptionsBuilder() .doNotDiscovery().build(); - PrismObject currentShadow; + PrismObject resourceObject; try { - currentShadow = shadowCache.getShadow(oldShadow.getOid(), oldShadow, options, ctx.getTask(), parentResult); + resourceObject = shadowCache.getShadow(oldShadow.getOid(), oldShadow, options, ctx.getTask(), parentResult); } catch (ObjectNotFoundException e) { // The object on the resource does not exist (any more?). LOGGER.warn("Object {} does not exist on the resource any more", oldShadow); throw e; // TODO } - change.setCurrentShadow(currentShadow); + LOGGER.trace("...taken from the resource:\n{}", resourceObject.debugDumpLazily()); + change.setCurrentResourceObject(resourceObject); } } } - assert change.getCurrentShadow() != null || change.isDelete(); - if (change.getCurrentShadow() != null) { - PrismObject currentShadow = shadowCache.completeShadow(ctx, change.getCurrentShadow(), oldShadow, false, parentResult); - change.setCurrentShadow(currentShadow); - // TODO: shadowState - shadowManager.updateShadow(ctx, currentShadow, oldShadow, null, parentResult); + assert change.getCurrentResourceObject() != null || change.isDelete(); + if (change.getCurrentResourceObject() != null) { + // TODO do we need to complete the shadow now? Why? MID-5834 + PrismObject currentShadow = shadowCache.completeShadow(ctx, change.getCurrentResourceObject(), oldShadow, false, parentResult); + change.setCurrentResourceObject(currentShadow); + // TODO: shadowState MID-5834 + shadowManager.updateShadow(ctx, currentShadow, change.getObjectDelta(), oldShadow, null, parentResult); } if (change.getObjectDelta() != null && change.getObjectDelta().getOid() == null) { @@ -268,10 +290,10 @@ private void preProcessChange(ProvisioningContext ctx, Change change, OperationR } if (change.isDelete()) { - PrismObject currentShadow = change.getCurrentShadow(); + PrismObject currentShadow = change.getCurrentResourceObject(); if (currentShadow == null) { currentShadow = oldShadow.clone(); - change.setCurrentShadow(currentShadow); // todo why is this? [pmed] + change.setCurrentResourceObject(currentShadow); // todo why is this? MID-5834 } if (!ShadowUtil.isDead(currentShadow) || ShadowUtil.isExists(currentShadow)) { shadowManager.markShadowTombstone(currentShadow, parentResult); @@ -279,13 +301,33 @@ private void preProcessChange(ProvisioningContext ctx, Change change, OperationR } } + /** + * Index-only items in the resource object delta are necessarily incomplete: their old value was taken from repo + * (i.e. was empty before delta application). We mark them as such. One of direct consequences is that updateShadow method + * will know that it cannot use this data to update cached (index-only) attributes in repo shadow. + */ + private void markIndexOnlyItemsAsIncomplete(PrismObject resourceObject, + ObjectDelta resourceObjectDelta, ProvisioningContext ctx) + throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException, + ExpressionEvaluationException { + RefinedObjectClassDefinition ocDef = ctx.computeCompositeObjectClassDefinition(resourceObject); + for (RefinedAttributeDefinition attrDef : ocDef.getAttributeDefinitions()) { + if (attrDef.isIndexOnly()) { + ItemPath path = ItemPath.create(ShadowType.F_ATTRIBUTES, attrDef.getItemName()); + LOGGER.trace("Marking item {} as incomplete because it's index-only", path); + //noinspection unchecked + resourceObject.findCreateItem(path, Item.class, attrDef, true).setIncomplete(true); + } + } + } + private ResourceObjectShadowChangeDescription createResourceShadowChangeDescription( Change change, ResourceType resourceType, String channel) { ResourceObjectShadowChangeDescription shadowChangeDescription = new ResourceObjectShadowChangeDescription(); shadowChangeDescription.setObjectDelta(change.getObjectDelta()); shadowChangeDescription.setResource(resourceType.asPrismObject()); - shadowChangeDescription.setOldShadow(change.getOldShadow()); - shadowChangeDescription.setCurrentShadow(change.getCurrentShadow()); + shadowChangeDescription.setOldShadow(change.getOldRepoShadow()); + shadowChangeDescription.setCurrentShadow(change.getCurrentResourceObject()); shadowChangeDescription.setSourceChannel(channel != null ? channel : SchemaConstants.CHANGE_CHANNEL_LIVE_SYNC_URI); return shadowChangeDescription; } diff --git a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdate.java b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdate.java index db6e0554e57..29961fdeaaa 100644 --- a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdate.java +++ b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdate.java @@ -15,7 +15,9 @@ import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.provisioning.api.ResourceObjectShadowChangeDescription; import com.evolveum.midpoint.provisioning.impl.AbstractProvisioningIntegrationTest; +import com.evolveum.midpoint.schema.GetOperationOptions; import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; +import com.evolveum.midpoint.schema.SelectorOptions; import com.evolveum.midpoint.schema.constants.MidPointConstants; import com.evolveum.midpoint.schema.internals.InternalsConfig; import com.evolveum.midpoint.schema.processor.ObjectClassComplexTypeDefinition; @@ -26,11 +28,13 @@ import com.evolveum.midpoint.schema.util.ResourceTypeUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.test.IntegrationTestTools; +import com.evolveum.midpoint.test.asserter.ShadowAsserter; import com.evolveum.midpoint.test.util.TestUtil; -import com.evolveum.midpoint.util.exception.SchemaException; +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 org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; @@ -47,7 +51,6 @@ import java.util.concurrent.TimeoutException; import static com.evolveum.midpoint.provisioning.impl.ProvisioningTestUtil.checkRepoAccountShadow; -import static com.evolveum.midpoint.provisioning.impl.ProvisioningTestUtil.checkRepoShadow; import static org.testng.AssertJUnit.*; /** @@ -67,7 +70,10 @@ public abstract class TestAsyncUpdate extends AbstractProvisioningIntegrationTes private static final String RESOURCE_ASYNC_OID = "fb04d113-ebf8-41b4-b13b-990a597d110b"; private static final File CHANGE_100 = new File(TEST_DIR, "change-100-banderson-first-occurrence.xml"); - private static final File CHANGE_110 = new File(TEST_DIR, "change-110-banderson-delta.xml"); + private static final File CHANGE_110 = new File(TEST_DIR, "change-110-banderson-delta-add-values.xml"); + private static final File CHANGE_112 = new File(TEST_DIR, "change-112-banderson-delta-add-more-values.xml"); + private static final File CHANGE_115 = new File(TEST_DIR, "change-115-banderson-delta-delete-values.xml"); + private static final File CHANGE_117 = new File(TEST_DIR, "change-117-banderson-delta-replace-values.xml"); private static final File CHANGE_120 = new File(TEST_DIR, "change-120-banderson-new-state.xml"); private static final File CHANGE_125 = new File(TEST_DIR, "change-125-banderson-notification-only.xml"); private static final File CHANGE_130 = new File(TEST_DIR, "change-130-banderson-delete.xml"); @@ -80,6 +86,8 @@ public abstract class TestAsyncUpdate extends AbstractProvisioningIntegrationTes private static final Trace LOGGER = TraceManager.getTrace(TestAsyncUpdate.class); private static final long TIMEOUT = 5000L; + private static final String ATTR_TEST = "test"; + private static final String ATTR_MEMBER_OF = "memberOf"; protected PrismObject resource; @@ -297,10 +305,135 @@ public void test110ListeningForValueAdd(ITestContext ctx) throws Exception { assertEquals("Wrong # of values added (second mod)", 6, iterator.next().getValuesToAdd().size()); assertNotNull("Current shadow is not present", lastChange.getCurrentShadow()); - PrismObject accountRepo = findAccountShadowByUsername("banderson", resource, result); - assertNotNull("Shadow is not present in the repository", accountRepo); - display("Repository shadow", accountRepo); - checkRepoShadow(accountRepo, ShadowKindType.ACCOUNT, getNumberOfAccountAttributes()); + ShadowAsserter asserter = getAndersonFull(false, task, result); + if (isCached()) { + asserter.attributes() + .attribute(ATTR_TEST).assertRealValues("value1", "value2", "value3").end() + .attribute(ATTR_MEMBER_OF).assertRealValues("group1", "group2", "group3", "group4", "group5", "group6").end(); + } + } + + @Test + public void test112ListeningForValueAddMore(ITestContext ctx) throws Exception { + Task task = getTask(ctx); + OperationResult result = getResult(ctx); + + prepareMessage(CHANGE_112); + + syncServiceMock.reset(); + + setDummyAccountTestAttribute("banderson", "value1", "value2", "value3", "value4"); + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_ASYNC_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + dumpAsyncUpdateListeningActivity(handle, task, result); + syncServiceMock.waitForNotifyChange(TIMEOUT); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + ResourceObjectShadowChangeDescription lastChange = syncServiceMock.getLastChange(); + display("The change", lastChange); + + PrismObject oldShadow = lastChange.getOldShadow(); + assertNotNull("Old shadow missing", oldShadow); + assertNotNull("Old shadow does not have an OID", oldShadow.getOid()); + + assertNotNull("Delta is missing", lastChange.getObjectDelta()); + assertTrue("Delta is not a MODIFY one", lastChange.getObjectDelta().isModify()); + Collection> modifications = lastChange.getObjectDelta().getModifications(); + assertEquals("Wrong # of modifications", 2, modifications.size()); + Iterator> iterator = modifications.iterator(); + assertEquals("Wrong # of values added (first mod)", 1, iterator.next().getValuesToAdd().size()); + assertEquals("Wrong # of values added (second mod)", 1, iterator.next().getValuesToAdd().size()); + assertNotNull("Current shadow is not present", lastChange.getCurrentShadow()); + + ShadowAsserter asserter = getAndersonFull(false, task, result); + if (isCached()) { + asserter.attributes() + .attribute(ATTR_TEST).assertRealValues("value1", "value2", "value3", "value4").end() + .attribute(ATTR_MEMBER_OF).assertRealValues("group1", "group2", "group3", "group4", "group5", "group6", "group7").end(); + } + } + + @Test // MID-5832 + public void test115ListeningForValueDelete(ITestContext ctx) throws Exception { + Task task = getTask(ctx); + OperationResult result = getResult(ctx); + + prepareMessage(CHANGE_115); + + syncServiceMock.reset(); + + setDummyAccountTestAttribute("banderson", "value1", "value3", "value4"); + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_ASYNC_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + dumpAsyncUpdateListeningActivity(handle, task, result); + syncServiceMock.waitForNotifyChange(TIMEOUT); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + ResourceObjectShadowChangeDescription lastChange = syncServiceMock.getLastChange(); + display("The change", lastChange); + + PrismObject oldShadow = lastChange.getOldShadow(); + assertNotNull("Old shadow missing", oldShadow); + assertNotNull("Old shadow does not have an OID", oldShadow.getOid()); + + assertNotNull("Delta is missing", lastChange.getObjectDelta()); + assertTrue("Delta is not a MODIFY one", lastChange.getObjectDelta().isModify()); + Collection> modifications = lastChange.getObjectDelta().getModifications(); + assertEquals("Wrong # of modifications", 2, modifications.size()); + Iterator> iterator = modifications.iterator(); + assertEquals("Wrong # of values deleted (first mod)", 1, iterator.next().getValuesToDelete().size()); + assertEquals("Wrong # of values deleted (second mod)", 2, iterator.next().getValuesToDelete().size()); + assertNotNull("Current shadow is not present", lastChange.getCurrentShadow()); + + ShadowAsserter asserter = getAndersonFull(false, task, result); + if (isCached()) { + asserter.attributes() + .attribute(ATTR_TEST).assertRealValues("value1", "value3", "value4").end() + .attribute(ATTR_MEMBER_OF).assertRealValues("group1", "group4", "group5", "group6", "group7").end(); + } + } + + @Test // MID-5832 + public void test117ListeningForValueReplace(ITestContext ctx) throws Exception { + Task task = getTask(ctx); + OperationResult result = getResult(ctx); + + prepareMessage(CHANGE_117); + + syncServiceMock.reset(); + + setDummyAccountTestAttribute("banderson", "value100"); + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_ASYNC_OID); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + dumpAsyncUpdateListeningActivity(handle, task, result); + syncServiceMock.waitForNotifyChange(TIMEOUT); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + ResourceObjectShadowChangeDescription lastChange = syncServiceMock.getLastChange(); + display("The change", lastChange); + + PrismObject oldShadow = lastChange.getOldShadow(); + assertNotNull("Old shadow missing", oldShadow); + assertNotNull("Old shadow does not have an OID", oldShadow.getOid()); + + assertNotNull("Delta is missing", lastChange.getObjectDelta()); + assertTrue("Delta is not a MODIFY one", lastChange.getObjectDelta().isModify()); + Collection> modifications = lastChange.getObjectDelta().getModifications(); + assertEquals("Wrong # of modifications", 2, modifications.size()); + Iterator> iterator = modifications.iterator(); + assertEquals("Wrong # of values replaced (first mod)", 1, iterator.next().getValuesToReplace().size()); + assertEquals("Wrong # of values replaced (second mod)", 2, iterator.next().getValuesToReplace().size()); + assertNotNull("Current shadow is not present", lastChange.getCurrentShadow()); + + ShadowAsserter asserter = getAndersonFull(false, task, result); + if (isCached()) { + asserter.attributes() + .attribute(ATTR_TEST).assertRealValues("value100").end() + .attribute(ATTR_MEMBER_OF).assertRealValues("group100", "group101").end(); + } } @Test @@ -330,10 +463,7 @@ public void test120ListeningForShadowReplace(ITestContext ctx) throws Exception assertNull("Delta is present although it should not be", lastChange.getObjectDelta()); assertNotNull("Current shadow is missing", lastChange.getCurrentShadow()); - PrismObject accountRepo = findAccountShadowByUsername("banderson", resource, result); - assertNotNull("Shadow is not present in the repository", accountRepo); - display("Repository shadow", accountRepo); - checkRepoShadow(accountRepo, ShadowKindType.ACCOUNT, getNumberOfAccountAttributes()); + ShadowAsserter asserter = getAndersonFull(false, task, result); } @Test @@ -370,10 +500,7 @@ public void test125ListeningForNotificationOnly(ITestContext ctx) throws Excepti display("change current shadow", lastChange.getCurrentShadow()); - PrismObject accountRepo = findAccountShadowByUsername("banderson", resource, result); - assertNotNull("Shadow is not present in the repository", accountRepo); - display("Repository shadow", accountRepo); - checkRepoShadow(accountRepo, ShadowKindType.ACCOUNT, getNumberOfAccountAttributes()); + ShadowAsserter asserter = getAndersonFull(false, task, result); } @Test @@ -403,10 +530,7 @@ public void test130ListeningForShadowDelete(ITestContext ctx) throws Exception { //assertNull("Current shadow is present while not expecting it", lastChange.getCurrentShadow()); //current shadow was added during the processing - PrismObject accountRepo = findAccountShadowByUsername("banderson", resource, result); - assertNotNull("Shadow is not present in the repository", accountRepo); - display("Repository shadow", accountRepo); - checkRepoShadow(accountRepo, ShadowKindType.ACCOUNT, getNumberOfAccountAttributes()); + getAndersonFull(true, task, result); } @SuppressWarnings("SameParameterValue") @@ -417,7 +541,11 @@ void addDummyAccount(String name) { void setDummyAccountTestAttribute(String name, String... values) { } - abstract int getNumberOfAccountAttributes(); + private int getNumberOfAccountAttributes() { + return isCached() ? 4 : 2; + } + + abstract boolean isCached(); boolean hasReadCapability() { return false; @@ -428,4 +556,40 @@ void prepareMessage(File messageFile) MockAsyncUpdateSource.INSTANCE.reset(); MockAsyncUpdateSource.INSTANCE.prepareMessage(prismContext.parserFor(messageFile).parseRealValue()); } + + @Contract("false,_,_ -> !null") + private ShadowAsserter getAndersonFull(boolean dead, Task task, OperationResult result) + throws SchemaException, ObjectNotFoundException, SecurityViolationException, CommunicationException, + ConfigurationException, ExpressionEvaluationException { + PrismObject shadowRepo = findAccountShadowByUsername("banderson", resource, result); + assertNotNull("No Anderson shadow in repo", shadowRepo); + Collection> options = schemaHelper.getOperationOptionsBuilder() + .noFetch() + .retrieve() + .build(); + try { + PrismObject shadow = provisioningService + .getObject(ShadowType.class, shadowRepo.getOid(), options, task, result); + if (dead) { + fail("Shadow should be gone now but it is not: " + shadow.debugDump()); + } + return assertShadow(shadow, "after") + .assertKind(ShadowKindType.ACCOUNT) + .attributes() + .assertSize(getNumberOfAccountAttributes()) + .primaryIdentifier() + .assertRealValues("banderson") + .end() + .secondaryIdentifier() + .assertRealValues("banderson") + .end() + .end(); + } catch (ObjectNotFoundException e) { + if (!dead) { + e.printStackTrace(); + fail("Shadow is gone but it should not be"); + } + return null; + } + } } diff --git a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdateCaching.java b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdateCaching.java index 9f677ab88ed..3ffcf680c6b 100644 --- a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdateCaching.java +++ b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdateCaching.java @@ -31,7 +31,7 @@ public List getConnectorTypes() { } @Override - protected int getNumberOfAccountAttributes() { - return 4; + boolean isCached() { + return true; } } diff --git a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdateCachingIndexOnly.java b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdateCachingIndexOnly.java index 5aac38ad198..ba4f2ac3711 100644 --- a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdateCachingIndexOnly.java +++ b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdateCachingIndexOnly.java @@ -29,11 +29,6 @@ protected File getResourceFile() { return RESOURCE_ASYNC_CACHING_INDEX_ONLY_FILE; } - @Override - protected int getNumberOfAccountAttributes() { - return 3; - } - @Override public void initSystem(Task initTask, OperationResult initResult) throws Exception { // These are experimental features, so they need to be explicitly enabled. This will be eliminated later, diff --git a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdateNoCaching.java b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdateNoCaching.java index b07c47f6cc1..3f8c69bc6bc 100644 --- a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdateNoCaching.java +++ b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/async/TestAsyncUpdateNoCaching.java @@ -49,8 +49,8 @@ public List getConnectorTypes() { } @Override - protected int getNumberOfAccountAttributes() { - return 2; + boolean isCached() { + return false; } @Override diff --git a/provisioning/provisioning-impl/src/test/resources/async/change-110-banderson-delta.xml b/provisioning/provisioning-impl/src/test/resources/async/change-110-banderson-delta-add-values.xml similarity index 100% rename from provisioning/provisioning-impl/src/test/resources/async/change-110-banderson-delta.xml rename to provisioning/provisioning-impl/src/test/resources/async/change-110-banderson-delta-add-values.xml diff --git a/provisioning/provisioning-impl/src/test/resources/async/change-112-banderson-delta-add-more-values.xml b/provisioning/provisioning-impl/src/test/resources/async/change-112-banderson-delta-add-more-values.xml new file mode 100644 index 00000000000..5e6893632a1 --- /dev/null +++ b/provisioning/provisioning-impl/src/test/resources/async/change-112-banderson-delta-add-more-values.xml @@ -0,0 +1,32 @@ + + + + ri:AccountObjectClass + + banderson + banderson + + + modify + ShadowType + + add + attributes/ri:test + value4 + + + add + attributes/ri:memberOf + group7 + + + diff --git a/provisioning/provisioning-impl/src/test/resources/async/change-115-banderson-delta-delete-values.xml b/provisioning/provisioning-impl/src/test/resources/async/change-115-banderson-delta-delete-values.xml new file mode 100644 index 00000000000..78bf974178e --- /dev/null +++ b/provisioning/provisioning-impl/src/test/resources/async/change-115-banderson-delta-delete-values.xml @@ -0,0 +1,33 @@ + + + + ri:AccountObjectClass + + banderson + banderson + + + modify + ShadowType + + delete + attributes/ri:test + value2 + + + delete + attributes/ri:memberOf + group2 + group3 + + + diff --git a/provisioning/provisioning-impl/src/test/resources/async/change-117-banderson-delta-replace-values.xml b/provisioning/provisioning-impl/src/test/resources/async/change-117-banderson-delta-replace-values.xml new file mode 100644 index 00000000000..dba7dc494d2 --- /dev/null +++ b/provisioning/provisioning-impl/src/test/resources/async/change-117-banderson-delta-replace-values.xml @@ -0,0 +1,33 @@ + + + + ri:AccountObjectClass + + banderson + banderson + + + modify + ShadowType + + replace + attributes/ri:test + value100 + + + replace + attributes/ri:memberOf + group100 + group101 + + + diff --git a/provisioning/provisioning-impl/testng-integration.xml b/provisioning/provisioning-impl/testng-integration.xml index 97b34a0a114..184269cea76 100644 --- a/provisioning/provisioning-impl/testng-integration.xml +++ b/provisioning/provisioning-impl/testng-integration.xml @@ -67,6 +67,7 @@ + diff --git a/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/Change.java b/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/Change.java index 94f5ad50e32..be5342c9b7f 100644 --- a/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/Change.java +++ b/provisioning/ucf-api/src/main/java/com/evolveum/midpoint/provisioning/ucf/api/Change.java @@ -23,60 +23,75 @@ */ public final class Change implements DebugDumpable { - private Collection> identifiers; - private Object primaryIdentifierRealValue; // we might reconsider this in the future - private ObjectClassComplexTypeDefinition objectClassDefinition; - private ObjectDelta objectDelta; - private PrismProperty token; - // TODO: maybe call this repoShadow? - private PrismObject oldShadow; - private PrismObject currentShadow; + /* + * Object identification: these values should be reasonably filled-in on Change object creation. + */ + private Object primaryIdentifierRealValue; // we might reconsider this in the future + private Collection> identifiers; + private ObjectClassComplexTypeDefinition objectClassDefinition; + + /* + * Usually either of the following two should be present. An exception is a notification-only change event. + */ + + /** + * This starts as a (converted) resource object. Gradually it gets augmented with shadow information, + * but the process needs to be clarified. See MID-5834. + */ + private PrismObject currentResourceObject; + + /** + * Delta from the resource - if known. Substantial e.g. for asynchronous updates. + */ + private ObjectDelta objectDelta; + + /** + * Token is used only in live synchronization process. + */ + private PrismProperty token; + + /** + * Original value of the corresponding shadow stored in repository. + * + * This is usually filled-in during change processing in provisioning module. (Except for notifyChange calls: TBD.) + */ + private PrismObject oldRepoShadow; /** * This means that the change is just a notification that a resource object has changed. To know about its state - * it has to be fetched. For notification-only changes the objectDelta and currentShadow has to be null. + * it has to be fetched. For notification-only changes both objectDelta and currentResourceObject have to be null. * (And this flag is introduced to distinguish intentional notification-only changes from malformed ones that have - * both currentShadow and objectDelta missing.) + * both currentResourceObject and objectDelta missing.) */ private boolean notificationOnly; - public Change(Object primaryIdentifierRealValue, Collection> identifiers, ObjectDelta change, - PrismProperty token) { - this.primaryIdentifierRealValue = primaryIdentifierRealValue; - this.identifiers = identifiers; - this.objectDelta = change; - this.currentShadow = null; - this.token = token; - } - + /** + * When token is present. + */ public Change(Object primaryIdentifierRealValue, Collection> identifiers, - PrismObject currentShadow, PrismProperty token) { + PrismObject currentResourceObject, ObjectDelta delta, + PrismProperty token) { this.primaryIdentifierRealValue = primaryIdentifierRealValue; this.identifiers = identifiers; - this.objectDelta = null; - this.currentShadow = currentShadow; + this.currentResourceObject = currentResourceObject; + this.objectDelta = delta; this.token = token; } - public Change(Object primaryIdentifierRealValue, Collection> identifiers, - PrismObject currentShadow, PrismObject oldShadow, ObjectDelta objectDelta) { - this.primaryIdentifierRealValue = primaryIdentifierRealValue; - this.identifiers = identifiers; - this.currentShadow = currentShadow; - this.oldShadow = oldShadow; - this.objectDelta = objectDelta; - } + /** + * When token is not present. + */ + public Change(Object primaryIdentifierRealValue, Collection> identifiers, + PrismObject currentResourceObject, ObjectDelta delta) { + this.primaryIdentifierRealValue = primaryIdentifierRealValue; + this.identifiers = identifiers; + this.currentResourceObject = currentResourceObject; + this.objectDelta = delta; + } private Change() { } - public static Change createNotificationOnly(Collection> identifiers) { - Change rv = new Change(); - rv.identifiers = identifiers; - rv.notificationOnly = true; - return rv; - } - public ObjectDelta getObjectDelta() { return objectDelta; } @@ -109,20 +124,20 @@ public void setToken(PrismProperty token) { this.token = token; } - public PrismObject getOldShadow() { - return oldShadow; + public PrismObject getOldRepoShadow() { + return oldRepoShadow; } - public void setOldShadow(PrismObject oldShadow) { - this.oldShadow = oldShadow; + public void setOldRepoShadow(PrismObject oldRepoShadow) { + this.oldRepoShadow = oldRepoShadow; } - public PrismObject getCurrentShadow() { - return currentShadow; + public PrismObject getCurrentResourceObject() { + return currentResourceObject; } - public void setCurrentShadow(PrismObject currentShadow) { - this.currentShadow = currentShadow; + public void setCurrentResourceObject(PrismObject currentResourceObject) { + this.currentResourceObject = currentResourceObject; } public void setNotificationOnly(boolean notificationOnly) { @@ -145,7 +160,7 @@ public boolean isAdd() { @Override public String toString() { return "Change(uid=" + primaryIdentifierRealValue + ",identifiers=" + identifiers + ", objectDelta=" + objectDelta + ", token=" + token - + ", oldShadow=" + oldShadow + ", currentShadow=" + currentShadow + ")"; + + ", oldRepoShadow=" + oldRepoShadow + ", currentResourceObject=" + currentResourceObject + ")"; } @Override @@ -172,19 +187,19 @@ public String debugDump(int indent) { sb.append("\n"); DebugUtil.debugDumpWithLabel(sb, "token", token, indent + 1); sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, "oldShadow", oldShadow, indent + 1); + DebugUtil.debugDumpWithLabel(sb, "oldRepoShadow", oldRepoShadow, indent + 1); sb.append("\n"); - DebugUtil.debugDumpWithLabel(sb, "currentShadow", currentShadow, indent + 1); + DebugUtil.debugDumpWithLabel(sb, "currentResourceObject", currentResourceObject, indent + 1); return sb.toString(); } public String getOid() { if (objectDelta != null && objectDelta.getOid() != null) { return objectDelta.getOid(); - } else if (currentShadow.getOid() != null) { - return currentShadow.getOid(); - } else if (oldShadow.getOid() != null) { - return oldShadow.getOid(); + } else if (currentResourceObject.getOid() != null) { + return currentResourceObject.getOid(); + } else if (oldRepoShadow.getOid() != null) { + return oldRepoShadow.getOid(); } else { throw new IllegalArgumentException("No oid value defined for the object to synchronize."); } diff --git a/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/async/TransformationalAsyncUpdateMessageListener.java b/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/async/TransformationalAsyncUpdateMessageListener.java index 2f9aafe4e35..d11e20e5226 100644 --- a/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/async/TransformationalAsyncUpdateMessageListener.java +++ b/provisioning/ucf-impl-builtin/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/builtin/async/TransformationalAsyncUpdateMessageListener.java @@ -48,7 +48,8 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; -import static com.evolveum.midpoint.schema.util.ObjectTypeUtil.toPrismObject; +import static com.evolveum.midpoint.schema.constants.SchemaConstants.CHANGE_CHANNEL_ASYNC_UPDATE_URI; +import static com.evolveum.midpoint.schema.util.ObjectTypeUtil.asPrismObject; import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; /** @@ -95,6 +96,7 @@ public boolean onMessage(AsyncUpdateMessageType message) throws SchemaException securityContextManager.setupPreAuthenticatedSecurityContext(authentication); Task task = taskManager.createTaskInstance(OP_ON_MESSAGE_PREPARATION); + task.setChannel(CHANGE_CHANNEL_ASYNC_UPDATE_URI); if (authentication != null && authentication.getPrincipal() instanceof MidPointPrincipal) { task.setOwner(((MidPointPrincipal) authentication.getPrincipal()).getUser().asPrismObject().clone()); } @@ -155,7 +157,7 @@ public boolean onMessage(AsyncUpdateMessageType message) throws SchemaException } finally { result.computeStatusIfUnknown(); if (result.isTraced()) { - tracer.storeTrace(task, result); + tracer.storeTrace(task, result, null); } } @@ -214,9 +216,9 @@ private Change createChange(UcfChangeType changeBean) throws SchemaException { setFromDefaults(changeBean.getObject(), objectClassName); Holder primaryIdentifierRealValueHolder = new Holder<>(); Collection> identifiers = getIdentifiers(changeBean, objectClassDef, primaryIdentifierRealValueHolder); - Change change = new Change(primaryIdentifierRealValueHolder.getValue(), identifiers, toPrismObject(changeBean.getObject()), null, delta); + Change change = new Change(primaryIdentifierRealValueHolder.getValue(), identifiers, asPrismObject(changeBean.getObject()), delta); change.setObjectClassDefinition(objectClassDef); - if (change.getCurrentShadow() == null && change.getObjectDelta() == null) { + if (change.getCurrentResourceObject() == null && change.getObjectDelta() == null) { change.setNotificationOnly(true); } return change; diff --git a/provisioning/ucf-impl-connid/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/connid/ConnectorInstanceConnIdImpl.java b/provisioning/ucf-impl-connid/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/connid/ConnectorInstanceConnIdImpl.java index f430c3b1dbb..f6f5e69d36c 100644 --- a/provisioning/ucf-impl-connid/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/connid/ConnectorInstanceConnIdImpl.java +++ b/provisioning/ucf-impl-connid/src/main/java/com/evolveum/midpoint/provisioning/ucf/impl/connid/ConnectorInstanceConnIdImpl.java @@ -2332,7 +2332,7 @@ private Change getChangeFromSyncDelta(ObjectClass requestConnIdObjectClass, ObjectDelta objectDelta = prismContext.deltaFactory().object().create(ShadowType.class, ChangeType.DELETE); Uid uid = connIdDelta.getUid(); Collection> identifiers = ConnIdUtil.convertToIdentifiers(uid, deltaObjectClass, resourceSchema); - change = new Change(uid.getUidValue(), identifiers, objectDelta, createTokenProperty(connIdDelta.getToken())); + change = new Change(uid.getUidValue(), identifiers, null, objectDelta, createTokenProperty(connIdDelta.getToken())); } else if (icfDeltaType == SyncDeltaType.CREATE || icfDeltaType == SyncDeltaType.CREATE_OR_UPDATE || icfDeltaType == SyncDeltaType.UPDATE) { @@ -2341,17 +2341,17 @@ private Change getChangeFromSyncDelta(ObjectClass requestConnIdObjectClass, PrismObjectDefinition objectDefinition = toShadowDefinition(deltaObjectClass); LOGGER.trace("Object definition: {}", objectDefinition); - PrismObject currentShadow = connIdConvertor.convertToResourceObject(connIdDelta.getObject(), + PrismObject currentResourceObject = connIdConvertor.convertToResourceObject(connIdDelta.getObject(), objectDefinition, false, caseIgnoreAttributeNames, legacySchema, result); - LOGGER.trace("Got current shadow: {}", currentShadow.debugDumpLazily()); - Collection> identifiers = ShadowUtil.getAllIdentifiers(currentShadow); + LOGGER.trace("Got (current) resource object: {}", currentResourceObject.debugDumpLazily()); + Collection> identifiers = ShadowUtil.getAllIdentifiers(currentResourceObject); if (icfDeltaType == SyncDeltaType.CREATE) { ObjectDelta objectDelta = prismContext.deltaFactory().object().create(ShadowType.class, ChangeType.ADD); - objectDelta.setObjectToAdd(currentShadow); - change = new Change(uid.getUidValue(), identifiers, objectDelta, createTokenProperty(connIdDelta.getToken())); + objectDelta.setObjectToAdd(currentResourceObject); + change = new Change(uid.getUidValue(), identifiers, null, objectDelta, createTokenProperty(connIdDelta.getToken())); } else { - change = new Change(uid.getUidValue(), identifiers, currentShadow, createTokenProperty(connIdDelta.getToken())); + change = new Change(uid.getUidValue(), identifiers, currentResourceObject, null, createTokenProperty(connIdDelta.getToken())); } } else { diff --git a/provisioning/ucf-impl-connid/src/test/java/com/evolveum/midpoint/provisioning/ucf/impl/connid/TestUcfDummy.java b/provisioning/ucf-impl-connid/src/test/java/com/evolveum/midpoint/provisioning/ucf/impl/connid/TestUcfDummy.java index ef7d1d24e51..392210febc8 100644 --- a/provisioning/ucf-impl-connid/src/test/java/com/evolveum/midpoint/provisioning/ucf/impl/connid/TestUcfDummy.java +++ b/provisioning/ucf-impl-connid/src/test/java/com/evolveum/midpoint/provisioning/ucf/impl/connid/TestUcfDummy.java @@ -441,9 +441,9 @@ public void test101FetchAddChange() throws Exception { AssertJUnit.assertEquals(1, changes.size()); Change change = changes.get(0); assertNotNull("null change", change); - PrismObject currentShadow = change.getCurrentShadow(); - assertNotNull("null current shadow", currentShadow); - PrismAsserts.assertParentConsistency(currentShadow); + PrismObject currentResourceObject = change.getCurrentResourceObject(); + assertNotNull("null current resource object", currentResourceObject); + PrismAsserts.assertParentConsistency(currentResourceObject); Collection> identifiers = change.getIdentifiers(); assertNotNull("null identifiers", identifiers); assertFalse("empty identifiers", identifiers.isEmpty()); diff --git a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/task/AbstractSearchIterativeResultHandler.java b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/task/AbstractSearchIterativeResultHandler.java index 32eb86f2f3f..9c5d8a82908 100644 --- a/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/task/AbstractSearchIterativeResultHandler.java +++ b/repo/repo-common/src/main/java/com/evolveum/midpoint/repo/common/task/AbstractSearchIterativeResultHandler.java @@ -418,7 +418,7 @@ private void processRequest(ProcessingRequest request, RunningTask workerTask, O } if (tracingRequested) { - taskManager.getTracer().storeTrace(workerTask, result); + taskManager.getTracer().storeTrace(workerTask, result, parentResult); TracingAppender.terminateCollecting(); // todo reconsider LevelOverrideTurboFilter.cancelLoggingOverride(); // todo reconsider } diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractHigherUnitTest.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractHigherUnitTest.java index d598245b25b..61f433c63b2 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractHigherUnitTest.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractHigherUnitTest.java @@ -95,7 +95,7 @@ import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; /** - * Abstract unit test for use in higher layes of the system (repo and abobe). + * Abstract unit test for use in higher layers of the system (repo and above). * Does not initialize any parts of the system except for prism. * Implements some common convenience methods. * diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java index d5c381776e9..d4ab02788ef 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java @@ -247,14 +247,22 @@ public void setTaskAndResult(ITestContext ctx, Method testMethod) throws SchemaE TestUtil.displayTestTitle(testShortName); Task task = createTask(testFullName); OperationResult rootResult = task.getResult(); + TracingProfileType tracingProfile = getTestMethodTracingProfile(); CompiledTracingProfile compiledTracingProfile = tracingProfile != null ? tracer.compileProfile(tracingProfile, rootResult) : null; OperationResult result = rootResult.subresult(testFullName + "Run") .tracingProfile(compiledTracingProfile) .build(); + + // This is quite a hack. We need to provide traced result to all clients that need to access it via the task. + // (I.e. not via the test context.) + task.setResult(result); + ctx.setAttribute(ATTR_TASK, task); ctx.setAttribute(ATTR_RESULT, result); + + MidpointTestMethodContext.setup(task, result); } protected TracingProfileType getTestMethodTracingProfile() { @@ -285,21 +293,73 @@ public void storeTraceIfRequested(ITestContext ctx, Method testMethod) { result.computeStatusIfUnknown(); if (result.isTraced()) { System.out.println("Storing the trace."); - tracer.storeTrace(task, result); + tracer.storeTrace(task, result, null); } task.getResult().computeStatusIfUnknown(); } } + + // Beware of any other after methods. They might need this context. + MidpointTestMethodContext.destroy(); } + @Deprecated protected OperationResult getResult(IAttributes attrs) { return (OperationResult) attrs.getAttribute(ATTR_RESULT); } + @Deprecated protected Task getTask(IAttributes attrs) { return (Task) attrs.getAttribute(ATTR_TASK); } + protected Task getTask() { + MidpointTestMethodContext ctx = MidpointTestMethodContext.get(); + if (ctx != null) { + return ctx.getTask(); + } else { + return createAdHocTestContext().getTask(); + } + } + + protected Task getOrCreateTask(String methodName) { + MidpointTestMethodContext ctx = MidpointTestMethodContext.get(); + if (ctx != null) { + return ctx.getTask(); + } else { + return taskManager.createTaskInstance(this.getClass().getName() + "." + methodName); + } + } + + protected OperationResult createSubresult(String methodName) { + String className = this.getClass().getName(); + MidpointTestMethodContext ctx = MidpointTestMethodContext.get(); + OperationResult parent; + if (ctx != null) { + parent = ctx.getResult(); + } else { + parent = new OperationResult(className + ".parent"); + } + return parent.createSubresult(className + "." + methodName); + } + + protected OperationResult getResult() { + MidpointTestMethodContext ctx = MidpointTestMethodContext.get(); + if (ctx != null) { + return ctx.getResult(); + } else { + return createAdHocTestContext().getResult(); + } + } + + @NotNull + private MidpointTestMethodContext createAdHocTestContext() { + LOGGER.warn("No test context for current thread: creating new"); + System.out.println("No test context for current thread: creating new"); + Task task = taskManager.createTaskInstance(this.getClass().getName() + ".unknownMethod"); + return MidpointTestMethodContext.setup(task, task.getResult()); + } + abstract public void initSystem(Task initTask, OperationResult initResult) throws Exception; /** diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/MidpointTestMethodContext.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/MidpointTestMethodContext.java new file mode 100644 index 00000000000..bd59f09c62b --- /dev/null +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/MidpointTestMethodContext.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 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.test; + +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.task.api.Task; + +/** + * Thread-local context for midPoint test method. + */ +public class MidpointTestMethodContext { + + private static final ThreadLocal testContextThreadLocal = new ThreadLocal<>(); + + /** + * Task used to execute the test. + */ + private final Task task; + + /** + * Top-level operation result for test execution. + */ + private final OperationResult result; + + private MidpointTestMethodContext(Task task, OperationResult result) { + this.task = task; + this.result = result; + } + + public Task getTask() { + return task; + } + + public OperationResult getResult() { + return result; + } + + public static MidpointTestMethodContext get() { + return testContextThreadLocal.get(); + } + + public static MidpointTestMethodContext setup(Task task, OperationResult result) { + MidpointTestMethodContext ctx = new MidpointTestMethodContext(task, result); + testContextThreadLocal.set(ctx); + return ctx; + } + + public static void destroy() { + testContextThreadLocal.remove(); + } +} diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ShadowAttributesAsserter.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ShadowAttributesAsserter.java index 3eb22b3ff42..08b1ad18eeb 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ShadowAttributesAsserter.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ShadowAttributesAsserter.java @@ -29,6 +29,7 @@ import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowAttributesType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; import com.evolveum.prism.xml.ns._public.types_3.RawType; +import org.apache.commons.collections4.CollectionUtils; /** * @author semancik @@ -90,13 +91,21 @@ public ShadowAttributesAsserter assertAttributes(QName... expectedAttributes) } // TODO: change to ShadowAttributeAsserter later - public PrismPropertyAsserter> attribute(String attrName) { - PrismProperty attribute = findAttribute(attrName); - PrismPropertyAsserter> asserter = new PrismPropertyAsserter<>(attribute, this, "attribute "+attrName+" in "+desc()); - copySetupTo(asserter); - return asserter; - } + public PrismPropertyAsserter> attribute(String attrName) { + PrismProperty attribute = findAttribute(attrName); + PrismPropertyAsserter> asserter = new PrismPropertyAsserter<>(attribute, this, "attribute "+attrName+" in "+desc()); + copySetupTo(asserter); + return asserter; + } + // TODO: change to ShadowAttributeAsserter later + public PrismPropertyAsserter> attribute(QName attrName) { + PrismProperty attribute = findAttribute(attrName); + PrismPropertyAsserter> asserter = new PrismPropertyAsserter<>(attribute, this, "attribute "+attrName+" in "+desc()); + copySetupTo(asserter); + return asserter; + } + public ShadowAttributesAsserter assertAny() { assertNotNull("No attributes container in "+desc(), getAttributesContainer()); PrismContainerValue containerValue = getAttributesContainer().getValue(); @@ -117,27 +126,41 @@ private String presentAttributeNames() { return sb.toString(); } + public PrismPropertyAsserter> primaryIdentifier() { + Collection> primaryIdentifiers = ShadowUtil.getPrimaryIdentifiers(getShadow()); + assertFalse("No primary identifier in "+desc(), CollectionUtils.isEmpty(primaryIdentifiers)); + assertEquals("Wrong # of primary identifiers in "+desc(), 1, primaryIdentifiers.size()); + return attribute(primaryIdentifiers.iterator().next().getElementName()); + } + public ShadowAttributesAsserter assertHasPrimaryIdentifier() { Collection> primaryIdentifiers = ShadowUtil.getPrimaryIdentifiers(getShadow()); - assertFalse("No primary identifiers in "+desc(), primaryIdentifiers.isEmpty()); + assertFalse("No primary identifiers in "+desc(), CollectionUtils.isEmpty(primaryIdentifiers)); return this; } - + public ShadowAttributesAsserter assertNoPrimaryIdentifier() { Collection> primaryIdentifiers = ShadowUtil.getPrimaryIdentifiers(getShadow()); - assertTrue("Unexpected primary identifiers in "+desc()+": "+primaryIdentifiers, primaryIdentifiers.isEmpty()); + assertTrue("Unexpected primary identifiers in "+desc()+": "+primaryIdentifiers, CollectionUtils.isEmpty(primaryIdentifiers)); return this; } - + + public PrismPropertyAsserter> secondaryIdentifier() { + Collection> secondaryIdentifiers = ShadowUtil.getSecondaryIdentifiers(getShadow()); + assertFalse("No secondary identifier in "+desc(), CollectionUtils.isEmpty(secondaryIdentifiers)); + assertEquals("Wrong # of secondary identifiers in "+desc(), 1, secondaryIdentifiers.size()); + return attribute(secondaryIdentifiers.iterator().next().getElementName()); + } + public ShadowAttributesAsserter assertHasSecondaryIdentifier() { Collection> secondaryIdentifiers = ShadowUtil.getSecondaryIdentifiers(getShadow()); - assertFalse("No secondary identifiers in "+desc(), secondaryIdentifiers.isEmpty()); + assertFalse("No secondary identifiers in "+desc(), CollectionUtils.isEmpty(secondaryIdentifiers)); return this; } public ShadowAttributesAsserter assertNoSecondaryIdentifier() { Collection> secondaryIdentifiers = ShadowUtil.getSecondaryIdentifiers(getShadow()); - assertTrue("Unexpected secondary identifiers in "+desc()+": "+secondaryIdentifiers, secondaryIdentifiers.isEmpty()); + assertTrue("Unexpected secondary identifiers in "+desc()+": "+secondaryIdentifiers, CollectionUtils.isEmpty(secondaryIdentifiers)); return this; } diff --git a/repo/task-api/src/main/java/com/evolveum/midpoint/task/api/Tracer.java b/repo/task-api/src/main/java/com/evolveum/midpoint/task/api/Tracer.java index b7f84c3ad2e..9fadd910c30 100644 --- a/repo/task-api/src/main/java/com/evolveum/midpoint/task/api/Tracer.java +++ b/repo/task-api/src/main/java/com/evolveum/midpoint/task/api/Tracer.java @@ -11,6 +11,7 @@ import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.xml.ns._public.common.common_3.TracingProfileType; +import org.jetbrains.annotations.Nullable; /** * @@ -22,8 +23,9 @@ public interface Tracer { * * @param task Task containing the context information necessary e.g. to derive name of the trace file. * @param result Result that is to be serialized and stored. + * @param parentResult Parent result where this operation should be recorded (if any). */ - void storeTrace(Task task, OperationResult result); + void storeTrace(Task task, OperationResult result, @Nullable OperationResult parentResult); /** * Resolves a tracing profile - i.e. replaces references to other (named) profiles with their content. diff --git a/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/tracing/TracerImpl.java b/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/tracing/TracerImpl.java index c602834f9e2..498cb0e92f3 100644 --- a/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/tracing/TracerImpl.java +++ b/repo/task-quartz-impl/src/main/java/com/evolveum/midpoint/task/quartzimpl/tracing/TracerImpl.java @@ -8,7 +8,7 @@ package com.evolveum.midpoint.task.quartzimpl.tracing; import com.evolveum.midpoint.prism.*; -import com.evolveum.midpoint.prism.equivalence.EquivalenceStrategy; +import com.evolveum.midpoint.prism.util.CloneUtil; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.repo.api.RepositoryService; import com.evolveum.midpoint.repo.api.SystemConfigurationChangeDispatcher; @@ -74,6 +74,8 @@ public class TracerImpl implements Tracer, SystemConfigurationChangeListener { private SystemConfigurationType systemConfiguration; // can be null during some tests + private static final String OP_STORE_TRACE = TracerImpl.class.getName() + ".storeTrace"; + private static final String MIDPOINT_HOME = System.getProperty("midpoint.home"); private static final String TRACE_DIR = MIDPOINT_HOME + "trace/"; private static final String ZIP_ENTRY_NAME = "trace.xml"; @@ -91,39 +93,57 @@ public void shutdown() { } @Override - public void storeTrace(Task task, OperationResult result) { - CompiledTracingProfile compiledTracingProfile = result.getTracingProfile(); - TracingProfileType tracingProfile = compiledTracingProfile.getDefinition(); - result.clearTracingProfile(); - - if (!Boolean.FALSE.equals(tracingProfile.isCreateTraceFile())) { - boolean zip = !Boolean.FALSE.equals(tracingProfile.isCompressOutput()); - Map templateParameters = createTemplateParameters(task, result); // todo evaluate lazily if needed - File file = createFileName(zip, tracingProfile, templateParameters); - try { - TracingOutputType tracingOutput = createTracingOutput(task, result, tracingProfile); - String xml = prismContext.xmlSerializer().serializeRealValue(tracingOutput); - if (zip) { - MiscUtil.writeZipFile(file, ZIP_ENTRY_NAME, xml, StandardCharsets.UTF_8); - LOGGER.info("Trace was written to {} ({} chars uncompressed)", file, xml.length()); - } else { - try (PrintWriter pw = new PrintWriter(new FileWriter(file))) { - pw.write(xml); - LOGGER.info("Trace was written to {} ({} chars)", file, xml.length()); + public void storeTrace(Task task, OperationResult result, @Nullable OperationResult parentResult) { + OperationResult thisOpResult; + if (parentResult != null) { + thisOpResult = parentResult.createMinorSubresult(OP_STORE_TRACE); + } else { + thisOpResult = new OperationResult(OP_STORE_TRACE); + } + + try { + CompiledTracingProfile compiledTracingProfile = result.getTracingProfile(); + TracingProfileType tracingProfile = compiledTracingProfile.getDefinition(); + result.clearTracingProfile(); + + if (!Boolean.FALSE.equals(tracingProfile.isCreateTraceFile())) { + boolean zip = !Boolean.FALSE.equals(tracingProfile.isCompressOutput()); + Map templateParameters = createTemplateParameters(task, + result); // todo evaluate lazily if needed + File file = createFileName(zip, tracingProfile, templateParameters); + try { + long start = System.currentTimeMillis(); + TracingOutputType tracingOutput = createTracingOutput(task, result, tracingProfile); + String xml = prismContext.xmlSerializer().serializeRealValue(tracingOutput); + if (zip) { + MiscUtil.writeZipFile(file, ZIP_ENTRY_NAME, xml, StandardCharsets.UTF_8); + LOGGER.info("Trace was written to {} ({} chars uncompressed) in {} milliseconds", file, xml.length(), + System.currentTimeMillis() - start); + } else { + try (PrintWriter pw = new PrintWriter(new FileWriter(file))) { + pw.write(xml); + LOGGER.info("Trace was written to {} ({} chars) in {} milliseconds", file, xml.length(), + System.currentTimeMillis() - start); + } } + if (!Boolean.FALSE.equals(tracingProfile.isCreateRepoObject())) { + ReportOutputType reportOutputObject = new ReportOutputType(prismContext) + .name(createObjectName(tracingProfile, templateParameters)) + .archetypeRef(SystemObjectsType.ARCHETYPE_TRACE.value(), ArchetypeType.COMPLEX_TYPE) + .filePath(file.getAbsolutePath()) + .nodeRef(ObjectTypeUtil.createObjectRef(taskManager.getLocalNode(), prismContext)); + repositoryService.addObject(reportOutputObject.asPrismObject(), null, thisOpResult); + } + } catch (IOException | SchemaException | ObjectAlreadyExistsException | RuntimeException e) { + LoggingUtils.logUnexpectedException(LOGGER, "Couldn't write trace ({})", e, file); + throw new SystemException(e); } - if (!Boolean.FALSE.equals(tracingProfile.isCreateRepoObject())) { - ReportOutputType reportOutputObject = new ReportOutputType(prismContext) - .name(createObjectName(tracingProfile, templateParameters)) - .archetypeRef(SystemObjectsType.ARCHETYPE_TRACE.value(), ArchetypeType.COMPLEX_TYPE) - .filePath(file.getAbsolutePath()) - .nodeRef(ObjectTypeUtil.createObjectRef(taskManager.getLocalNode(), prismContext)); - repositoryService.addObject(reportOutputObject.asPrismObject(), null, result); - } - } catch (IOException | SchemaException | ObjectAlreadyExistsException | RuntimeException e) { - LoggingUtils.logUnexpectedException(LOGGER, "Couldn't write trace ({})", e, file); - throw new SystemException(e); } + } catch (Throwable t) { + thisOpResult.recordFatalError(t); + throw t; + } finally { + thisOpResult.computeStatusIfUnknown(); } } @@ -135,11 +155,22 @@ private TracingOutputType createTracingOutput(Task task, OperationResult result, output.setEnvironment(createTracingEnvironmentDescription(task, tracingProfile)); OperationResultType resultBean = result.createOperationResultType(); - output.setDictionary(extractDictionary(resultBean)); + + List embeddedDictionaries = extractDictionaries(result); + TraceDictionaryType dictionary = extractDictionary(embeddedDictionaries, resultBean); + output.setDictionary(dictionary); + result.setExtractedDictionary(dictionary); output.setResult(resultBean); return output; } + private List extractDictionaries(OperationResult result) { + return result.getResultStream() + .filter(r -> r.getExtractedDictionary() != null) + .map(OperationResult::getExtractedDictionary) + .collect(Collectors.toList()); + } + @NotNull private TracingEnvironmentType createTracingEnvironmentDescription(Task task, TracingProfileType tracingProfile) { TracingEnvironmentType environment = new TracingEnvironmentType(prismContext); @@ -171,11 +202,17 @@ private TracingEnvironmentType createTracingEnvironmentDescription(Task task, Tr // raw versions of ObjectReferenceType holding full object private static class ExtractingVisitor implements Visitor { + private final long started = System.currentTimeMillis(); private final TraceDictionaryType dictionary; + private final int dictionaryId; private final PrismContext prismContext; + private int comparisons = 0; + private int objectsChecked = 0; + private int objectsAdded = 0; - private ExtractingVisitor(TraceDictionaryType dictionary, PrismContext prismContext) { + private ExtractingVisitor(TraceDictionaryType dictionary, int dictionaryId, PrismContext prismContext) { this.dictionary = dictionary; + this.dictionaryId = dictionaryId; this.prismContext = prismContext; } @@ -198,42 +235,97 @@ public void visit(Visitable visitable) { //noinspection unchecked PrismObject object = refVal.getObject(); if (object != null && !object.isEmpty()) { - long entryId = findOrCreateEntry(object); + String qualifiedEntryId = findOrCreateEntry(object); refVal.setObject(null); - refVal.setOid(SchemaConstants.TRACE_DICTIONARY_PREFIX + entryId); + refVal.setOid(SchemaConstants.TRACE_DICTIONARY_PREFIX + qualifiedEntryId); + if (object.getDefinition() != null) { + refVal.setTargetType(object.getDefinition().getTypeName()); + } } } } - private long findOrCreateEntry(PrismObject object) { - long max = 0; + private String findOrCreateEntry(PrismObject object) { + long started = System.currentTimeMillis(); + int maxEntryId = 0; + + objectsChecked++; + PrismObject objectToStore = stripFetchResult(object); + for (TraceDictionaryEntryType entry : dictionary.getEntry()) { PrismObject dictionaryObject = entry.getObject().asReferenceValue().getObject(); - if (Objects.equals(object.getOid(), dictionaryObject.getOid()) && // todo remove if POV implements this - Objects.equals(object.getVersion(), dictionaryObject.getVersion()) && // todo remove if POV implements this - object.equals(dictionaryObject, EquivalenceStrategy.LITERAL)) { - return entry.getIdentifier(); + if (Objects.equals(objectToStore.getOid(), dictionaryObject.getOid()) && + Objects.equals(objectToStore.getVersion(), dictionaryObject.getVersion())) { + comparisons++; + boolean equals = objectToStore.equals(dictionaryObject); + if (equals) { + LOGGER.trace("Found existing entry #{}:{}: {} [in {} ms]", entry.getOriginDictionaryId(), + entry.getIdentifier(), objectToStore, System.currentTimeMillis() - started); + return entry.getOriginDictionaryId() + ":" + entry.getIdentifier(); + } else { + LOGGER.trace("Found object with the same OID {} and same version '{}' but with different content", + objectToStore.getOid(), objectToStore.getVersion()); + } } - if (entry.getIdentifier() > max) { - max = entry.getIdentifier(); + if (entry.getIdentifier() > maxEntryId) { + // We intentionally ignore context for entries. + maxEntryId = entry.getIdentifier(); } } - long newId = max + 1; -// System.out.println("Inserting object as entry #" + newId + ": " + object); + int newEntryId = maxEntryId + 1; + LOGGER.trace("Inserting object as entry #{}:{}: {} [in {} ms]", dictionaryId, newEntryId, objectToStore, + System.currentTimeMillis()-started); + dictionary.beginEntry() - .identifier(newId) - .object(ObjectTypeUtil.createObjectRefWithFullObject(object, prismContext)); - return newId; + .identifier(newEntryId) + .originDictionaryId(dictionaryId) + .object(ObjectTypeUtil.createObjectRefWithFullObject(objectToStore, prismContext)); + objectsAdded++; + return dictionaryId + ":" + newEntryId; + } + + @NotNull + private PrismObject stripFetchResult(PrismObject object) { + PrismObject objectToStore; + if (object.asObjectable().getFetchResult() != null) { + objectToStore = object.clone(); + objectToStore.asObjectable().setFetchResult(null); + } else { + objectToStore = object; + } + return objectToStore; + } + + private void logDiagnosticInformation() { + LOGGER.trace("Extracted dictionary: {} objects added in {} ms ({} total), using {} object comparisons while checking {} objects", + objectsAdded, System.currentTimeMillis()-started, dictionary.getEntry().size(), comparisons, objectsChecked); } } - private TraceDictionaryType extractDictionary(OperationResultType resultBean) { + private TraceDictionaryType extractDictionary(List embeddedDictionaries, OperationResultType resultBean) { TraceDictionaryType dictionary = new TraceDictionaryType(prismContext); - ExtractingVisitor extractingVisitor = new ExtractingVisitor(dictionary, prismContext); + + embeddedDictionaries.forEach(embeddedDictionary -> + dictionary.getEntry().addAll(CloneUtil.cloneCollectionMembers(embeddedDictionary.getEntry()))); + + int newDictionaryId = generateDictionaryId(embeddedDictionaries); + dictionary.setIdentifier(newDictionaryId); + + ExtractingVisitor extractingVisitor = new ExtractingVisitor(dictionary, newDictionaryId, prismContext); extractDictionary(resultBean, extractingVisitor); + extractingVisitor.logDiagnosticInformation(); + return dictionary; } + private int generateDictionaryId(List embedded) { + int max = embedded.stream() + .map(TraceDictionaryType::getIdentifier) + .max(Integer::compareTo) + .orElse(0); + return max + 1; + } + private void extractDictionary(OperationResultType resultBean, ExtractingVisitor extractingVisitor) { resultBean.getTrace().forEach(trace -> trace.asPrismContainerValue().accept(extractingVisitor)); resultBean.getPartialResults().forEach(partialResult -> extractDictionary(partialResult, extractingVisitor)); diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/scenarios/CaseTests.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/scenarios/CaseTests.java index 04da2f4d935..937c237d1af 100644 --- a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/scenarios/CaseTests.java +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/scenarios/CaseTests.java @@ -115,7 +115,7 @@ public void isCaseCreated(){ .inputValue(ConstantsUtil.CASE_CREATION_TEST_CASE_NAME) .updateSearch() .and() - .clickByName(ConstantsUtil.CASE_CREATION_TEST_CASE_NAME); + .containsLinkTextPartially(ConstantsUtil.CASE_CREATION_TEST_CASE_NAME); } diff --git a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/scenarios/ObjectListArchetypeTests.java b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/scenarios/ObjectListArchetypeTests.java index 0337fca83dc..ac15975f33d 100644 --- a/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/scenarios/ObjectListArchetypeTests.java +++ b/testing/schrodingertest/src/test/java/com/evolveum/midpoint/testing/schrodinger/scenarios/ObjectListArchetypeTests.java @@ -57,19 +57,23 @@ public void configureArchetypeObjectListView(){ prismForm .expandContainerPropertiesPanel(OBJECT_COLLECTION_VIEWS_HEADER) .addNewContainerValue(OBJECT_COLLECTION_VIEW_HEADER, NEW_GUI_OBJECT_LIST_VIEW_HEADER) - .expandContainerPropertiesPanel(NEW_OBJECT_LIST_VIEW_CONTAINER_KEY) + .expandContainerPropertiesPanel(NEW_OBJECT_LIST_VIEW_CONTAINER_NEW_VALUE_KEY) .expandContainerPropertiesPanel(COLLECTION_HEADER); //set UserType - SelenideElement newGuiObjectListViewPropertiesPanel = prismForm.getPrismPropertiesPanel(NEW_OBJECT_LIST_VIEW_CONTAINER_KEY); + SelenideElement newGuiObjectListViewPropertiesPanel = prismForm.getPrismPropertiesPanel(NEW_OBJECT_LIST_VIEW_CONTAINER_NEW_VALUE_KEY); + //todo fix! it takes Type input from the first collection container! newGuiObjectListViewPropertiesPanel .$(Schrodinger.byDataResourceKey("Type")) .$(By.tagName("select")) .selectOptionContainingText("User"); //set archetypeRef - SelenideElement collectionRefPropertyPanel = prismForm.findProperty(COLLECTION_REF_ATTRIBUTE_NAME); - collectionRefPropertyPanel + newGuiObjectListViewPropertiesPanel + .$(Schrodinger.byElementValue("span", COLLECTION_REF_ATTRIBUTE_NAME)) + .parent() + .parent() + .parent() .$(Schrodinger.byDataId("edit")) .click(); diff --git a/testing/schrodingertest/testng-integration-schrodinger.xml b/testing/schrodingertest/testng-integration-schrodinger.xml index 3428a8a88b5..8363aa78827 100644 --- a/testing/schrodingertest/testng-integration-schrodinger.xml +++ b/testing/schrodingertest/testng-integration-schrodinger.xml @@ -42,4 +42,9 @@ + + + + + diff --git a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/grouper/TestGrouperAsyncUpdate.java b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/grouper/TestGrouperAsyncUpdate.java index fc2984b4687..eb3254bcac3 100644 --- a/testing/story/src/test/java/com/evolveum/midpoint/testing/story/grouper/TestGrouperAsyncUpdate.java +++ b/testing/story/src/test/java/com/evolveum/midpoint/testing/story/grouper/TestGrouperAsyncUpdate.java @@ -6,19 +6,22 @@ */ package com.evolveum.midpoint.testing.story.grouper; +import com.evolveum.icf.dummy.resource.DummyGroup; +import com.evolveum.icf.dummy.resource.DummyResource; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.path.ItemName; import com.evolveum.midpoint.schema.GetOperationOptions; import com.evolveum.midpoint.schema.ResourceShadowDiscriminator; import com.evolveum.midpoint.schema.SelectorOptions; import com.evolveum.midpoint.schema.constants.MidPointConstants; +import com.evolveum.midpoint.schema.internals.InternalCounters; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.test.DummyResourceContoller; import com.evolveum.midpoint.test.TestResource; import com.evolveum.midpoint.test.asserter.ShadowAttributesAsserter; import com.evolveum.midpoint.test.asserter.prism.PrismPropertyAsserter; import com.evolveum.midpoint.test.util.MidPointTestConstants; -import com.evolveum.midpoint.test.util.TestUtil; import com.evolveum.midpoint.testing.story.AbstractStoryTest; import com.evolveum.midpoint.util.exception.*; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; @@ -69,6 +72,8 @@ public class TestGrouperAsyncUpdate extends AbstractStoryTest { private static final TestResource USER_BANDERSON = new TestResource(TEST_DIR, "user-banderson.xml", "4f439db5-181e-4297-9f7d-b3115524dbe8"); private static final TestResource USER_JLEWIS685 = new TestResource(TEST_DIR, "user-jlewis685.xml", "8b7bd936-b863-45d0-aabe-734fa3e22081"); + private static final TestResource TASK_GROUP_SCAVENGER = new TestResource(TEST_DIR, "task-group-scavenger.xml", "1d7bef40-953e-443e-8e9a-ec6e313668c4"); + private static final String NS_EXT = "http://grouper-demo.tier.internet2.edu"; public static final QName EXT_GROUPER_NAME = new QName(NS_EXT, "grouperName"); public static final QName EXT_LDAP_DN = new QName(NS_EXT, "ldapDn"); @@ -78,23 +83,34 @@ public class TestGrouperAsyncUpdate extends AbstractStoryTest { private static final String NOBODY_USERNAME = "nobody"; private static final String ALUMNI_NAME = "ref:affiliation:alumni"; + private static final String STAFF_NAME = "ref:affiliation:staff"; private static final File CHANGE_110 = new File(TEST_DIR, "change-110-alumni-add.json"); + private static final File CHANGE_115 = new File(TEST_DIR, "change-115-staff-add.json"); private static final File CHANGE_200 = new File(TEST_DIR, "change-200-banderson-add-alumni.json"); + private static final File CHANGE_210 = new File(TEST_DIR, "change-210-banderson-add-staff.json"); private static final File CHANGE_220 = new File(TEST_DIR, "change-220-jlewis685-add-alumni.json"); + private static final File CHANGE_221 = new File(TEST_DIR, "change-221-jlewis685-add-staff.json"); private static final File CHANGE_230 = new File(TEST_DIR, "change-230-nobody-add-alumni.json"); private static final File CHANGE_250 = new File(TEST_DIR, "change-250-banderson-delete-alumni.json"); + private static final File CHANGE_310 = new File(TEST_DIR, "change-310-staff-delete.json"); - private static final ItemName ATTR_MEMBER = new ItemName(MidPointConstants.NS_RI, "member"); + private static final ItemName ATTR_MEMBER = new ItemName(MidPointConstants.NS_RI, "members"); private static final String DN_BANDERSON = "uid=banderson,ou=people,dc=example,dc=com"; private static final String DN_JLEWIS685 = "uid=jlewis685,ou=people,dc=example,dc=com"; - public static final String DN_ALUMNI = "cn=alumni,ou=Affiliations,ou=Groups,dc=example,dc=com"; + private static final String DN_ALUMNI = "cn=alumni,ou=Affiliations,ou=Groups,dc=example,dc=com"; + private static final String DN_STAFF = "cn=staff,ou=Affiliations,ou=Groups,dc=example,dc=com"; + private static final String GROUPER_DUMMY_RESOURCE_ID = "grouper"; private PrismObject resourceGrouper; + private DummyResourceContoller grouperResourceCtl; + private DummyResource grouperDummyResource; + private PrismObject resourceLdap; - private String alumniOid; + private String orgAlumniOid; + private String orgStaffOid; @Override public void initSystem(Task initTask, OperationResult initResult) throws Exception { @@ -110,7 +126,12 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti repoAddObject(LIB_GROUPER, initResult); + grouperResourceCtl = DummyResourceContoller.create(GROUPER_DUMMY_RESOURCE_ID); + grouperResourceCtl.populateWithDefaultSchema(); + grouperDummyResource = grouperResourceCtl.getDummyResource(); resourceGrouper = importAndGetObjectFromFile(ResourceType.class, RESOURCE_GROUPER.file, RESOURCE_GROUPER.oid, initTask, initResult); + grouperResourceCtl.setResource(resourceGrouper); + resourceLdap = importAndGetObjectFromFile(ResourceType.class, RESOURCE_LDAP.file, RESOURCE_LDAP.oid, initTask, initResult); openDJController.setResource(resourceLdap); @@ -123,9 +144,22 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti addObject(ROLE_LDAP_BASIC, initTask, initResult); addObject(TEMPLATE_USER, initTask, initResult); + addObject(TASK_GROUP_SCAVENGER, initTask, initResult); + setGlobalTracingOverride(createModelAndProvisioningLoggingTracingProfile()); } + @Override + protected boolean isAutoTaskManagementEnabled() { + return true; + } + + @Override + protected TracingProfileType getTestMethodTracingProfile() { + return createModelAndProvisioningLoggingTracingProfile() + .fileNamePattern(TEST_METHOD_TRACING_FILENAME_PATTERN); + } + @Override protected File getSystemConfigurationFile() { return SYSTEM_CONFIGURATION_FILE; @@ -143,9 +177,7 @@ public static void stopResources() throws Exception { @Test public void test000Sanity() throws Exception { - final String TEST_NAME = "test000Sanity"; - TestUtil.displayTestTitle(this, TEST_NAME); - Task task = createTestTask(TEST_NAME); + Task task = getTask(); assertSuccess(modelService.testResource(RESOURCE_LDAP.oid, task)); assertSuccess(modelService.testResource(RESOURCE_GROUPER.oid, task)); @@ -153,10 +185,8 @@ public void test000Sanity() throws Exception { @Test public void test010CreateUsers() throws Exception { - final String TEST_NAME = "test010CreateUsers"; - TestUtil.displayTestTitle(this, TEST_NAME); - Task task = createTestTask(TEST_NAME); - OperationResult result = task.getResult(); + Task task = getTask(); + OperationResult result = getResult(); addObject(USER_BANDERSON, task, result); addObject(USER_JLEWIS685, task, result); @@ -172,15 +202,14 @@ public void test010CreateUsers() throws Exception { */ @Test public void test110AddAlumni() throws Exception { - final String TEST_NAME = "test110AddAlumni"; - TestUtil.displayTestTitle(this, TEST_NAME); - Task task = createTestTask(TEST_NAME); - OperationResult result = task.getResult(); + Task task = getTask(); + OperationResult result = getResult(); // GIVEN MockAsyncUpdateSource.INSTANCE.reset(); MockAsyncUpdateSource.INSTANCE.prepareMessage(getAmqp091Message(CHANGE_110)); + grouperDummyResource.addGroup(new DummyGroup(ALUMNI_NAME)); // WHEN @@ -190,20 +219,84 @@ public void test110AddAlumni() throws Exception { // THEN - result.computeStatus(); - TestUtil.assertSuccess(result); + assertSuccess(result); assertMembers(ALUMNI_NAME, task, result); - alumniOid = assertOrgByName("affiliation_alumni", "alumni after") + orgAlumniOid = assertOrgByName("affiliation_alumni", "alumni after") + .display() + .assertLifecycleState("active") .extension() - .property(EXT_GROUPER_NAME).singleValue().assertValue("ref:affiliation:alumni").end().end() + .property(EXT_GROUPER_NAME).singleValue().assertValue(ALUMNI_NAME).end().end() .property(EXT_LDAP_DN).singleValue().assertValue(DN_ALUMNI).end().end() .end() .assertAssignments(1) // archetype, todo assert target .assertDisplayName("Affiliation: alumni") .assertIdentifier("alumni") .assertLinks(2) // todo assert details + .links() + .projectionOnResource(RESOURCE_GROUPER.oid) + .target() + .assertNotDead() + .end() + .end() + .projectionOnResource(RESOURCE_LDAP.oid) + .target() + .assertNotDead() + .end() + .end() + .end() + .getOid(); + } + + /** + * GROUP_ADD event for ref:affiliation:staff. + */ + @Test + public void test115AddStaff() throws Exception { + Task task = getTask(); + OperationResult result = getResult(); + + // GIVEN + + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(getAmqp091Message(CHANGE_115)); + grouperDummyResource.addGroup(new DummyGroup(STAFF_NAME)); + // WHEN + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_GROUPER.oid); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + // THEN + + assertSuccess(result); + + assertMembers(STAFF_NAME, task, result); + + orgStaffOid = assertOrgByName("affiliation_staff", "staff after") + .display() + .assertLifecycleState("active") + .extension() + .property(EXT_GROUPER_NAME).singleValue().assertValue(STAFF_NAME).end().end() + .property(EXT_LDAP_DN).singleValue().assertValue(DN_STAFF).end().end() + .end() + .assertAssignments(1) // archetype, todo assert target + .assertDisplayName("Affiliation: staff") + .assertIdentifier("staff") + .assertLinks(2) // todo assert details + .links() + .projectionOnResource(RESOURCE_GROUPER.oid) + .target() + .assertNotDead() + .end() + .end() + .projectionOnResource(RESOURCE_LDAP.oid) + .target() + .assertNotDead() + .end() + .end() + .end() .getOid(); } @@ -212,15 +305,16 @@ public void test110AddAlumni() throws Exception { */ @Test public void test200AddAlumniForAnderson() throws Exception { - final String TEST_NAME = "test200AddAlumniForAnderson"; - TestUtil.displayTestTitle(this, TEST_NAME); - Task task = createTestTask(TEST_NAME); - OperationResult result = task.getResult(); + Task task = getTask(); + OperationResult result = getResult(); // GIVEN MockAsyncUpdateSource.INSTANCE.reset(); MockAsyncUpdateSource.INSTANCE.prepareMessage(getAmqp091Message(CHANGE_200)); + grouperDummyResource.getGroupByName(ALUMNI_NAME).addMember(BANDERSON_USERNAME); + + rememberCounter(InternalCounters.CONNECTOR_OPERATION_COUNT); // WHEN @@ -230,25 +324,87 @@ public void test200AddAlumniForAnderson() throws Exception { // THEN - result.computeStatus(); - TestUtil.assertSuccess(result); + assertSuccess(result); assertMembers(ALUMNI_NAME, task, result, BANDERSON_USERNAME); assertUserAfterByUsername(BANDERSON_USERNAME) .triggers() .assertTriggers(1); + + // Async update is not counted as a connector operation (at least not now). We should have no other ops, + // in particular we do NOT want the clockwork to run! (MID-5853) + assertCounterIncrement(InternalCounters.CONNECTOR_OPERATION_COUNT, 0); } /** - * Anderson should obtain an assignment. + * Anderson should obtain the assignment. */ @Test public void test202RecomputeAnderson() throws Exception { - final String TEST_NAME = "test202RecomputeAnderson"; - TestUtil.displayTestTitle(this, TEST_NAME); - Task task = createTestTask(TEST_NAME); - OperationResult result = task.getResult(); + Task task = getTask(); + OperationResult result = getResult(); + + // WHEN + + recomputeUser(USER_BANDERSON.oid, task, result); + + // THEN + + assertSuccess(result); + + assertUserAfterByUsername(BANDERSON_USERNAME) + .assignments() + .assertAssignments(2) + .assertRole(ROLE_LDAP_BASIC.oid) + .assertOrg(orgAlumniOid) + .end() + .links() + .assertLinks(1) + .projectionOnResource(resourceLdap.getOid()); + + openDJController.assertUniqueMember(DN_ALUMNI, DN_BANDERSON); + } + + /** + * Adding ref:affiliation:staff membership for banderson. + */ + @Test + public void test210AddStaffForAnderson() throws Exception { + Task task = getTask(); + OperationResult result = getResult(); + + // GIVEN + + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(getAmqp091Message(CHANGE_210)); + grouperDummyResource.getGroupByName(STAFF_NAME).addMember(BANDERSON_USERNAME); + + // WHEN + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_GROUPER.oid); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + // THEN + + assertSuccess(result); + + assertMembers(ALUMNI_NAME, task, result, BANDERSON_USERNAME); + assertMembers(STAFF_NAME, task, result, BANDERSON_USERNAME); + + assertUserAfterByUsername(BANDERSON_USERNAME) + .triggers() + .assertTriggers(1); + } + + /** + * Anderson should obtain the second assignment. + */ + @Test + public void test212RecomputeAnderson() throws Exception { + Task task = getTask(); + OperationResult result = getResult(); // WHEN @@ -256,36 +412,37 @@ public void test202RecomputeAnderson() throws Exception { // THEN - result.computeStatus(); - TestUtil.assertSuccess(result); + assertSuccess(result); assertUserAfterByUsername(BANDERSON_USERNAME) .assignments() - .assertAssignments(2) - .assertRole(ROLE_LDAP_BASIC.oid) - .assertOrg(alumniOid) + .assertAssignments(3) + .assertRole(ROLE_LDAP_BASIC.oid) + .assertOrg(orgAlumniOid) + .assertOrg(orgStaffOid) .end() .links() .assertLinks(1) .projectionOnResource(resourceLdap.getOid()); openDJController.assertUniqueMember(DN_ALUMNI, DN_BANDERSON); + openDJController.assertUniqueMember(DN_STAFF, DN_BANDERSON); } + /** * Adding ref:affiliation:alumni membership for jlewis685. */ @Test public void test220AddAlumniForLewis() throws Exception { - final String TEST_NAME = "test220AddAlumniForLewis"; - TestUtil.displayTestTitle(this, TEST_NAME); - Task task = createTestTask(TEST_NAME); - OperationResult result = task.getResult(); + Task task = getTask(); + OperationResult result = getResult(); // GIVEN MockAsyncUpdateSource.INSTANCE.reset(); MockAsyncUpdateSource.INSTANCE.prepareMessage(getAmqp091Message(CHANGE_220)); + grouperDummyResource.getGroupByName(ALUMNI_NAME).addMember(JLEWIS685_USERNAME); // WHEN @@ -295,8 +452,7 @@ public void test220AddAlumniForLewis() throws Exception { // THEN - result.computeStatus(); - TestUtil.assertSuccess(result); + assertSuccess(result); assertMembers(ALUMNI_NAME, task, result, BANDERSON_USERNAME, JLEWIS685_USERNAME); @@ -305,20 +461,82 @@ public void test220AddAlumniForLewis() throws Exception { .assertTriggers(1); } + /** + * Adding ref:affiliation:staff membership for jlewis685. + */ + @Test + public void test221AddStaffForLewis() throws Exception { + Task task = getTask(); + OperationResult result = getResult(); + + // GIVEN + + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(getAmqp091Message(CHANGE_221)); + grouperDummyResource.getGroupByName(STAFF_NAME).addMember(JLEWIS685_USERNAME); + + // WHEN + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_GROUPER.oid); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + // THEN + + assertSuccess(result); + + assertMembers(ALUMNI_NAME, task, result, BANDERSON_USERNAME, JLEWIS685_USERNAME); + assertMembers(STAFF_NAME, task, result, BANDERSON_USERNAME, JLEWIS685_USERNAME); + + assertUserAfterByUsername(JLEWIS685_USERNAME) + .triggers() + .assertTriggers(1); + } + + /** + * Lewis should obtain two assignments. + */ + @Test + public void test222RecomputeLewis() throws Exception { + Task task = getTask(); + OperationResult result = getResult(); + + // WHEN + + recomputeUser(USER_JLEWIS685.oid, task, result); + + // THEN + + assertSuccess(result); + + assertUserAfterByUsername(JLEWIS685_USERNAME) + .assignments() + .assertAssignments(3) + .assertRole(ROLE_LDAP_BASIC.oid) + .assertOrg(orgAlumniOid) + .assertOrg(orgStaffOid) + .end() + .links() + .assertLinks(1) + .projectionOnResource(resourceLdap.getOid()); + + openDJController.assertUniqueMember(DN_ALUMNI, DN_JLEWIS685); + openDJController.assertUniqueMember(DN_STAFF, DN_JLEWIS685); + } + /** * Adding ref:affiliation:alumni membership for non-existing user (nobody). */ @Test public void test230AddAlumniForNobody() throws Exception { - final String TEST_NAME = "test230AddAlumniForNobody"; - TestUtil.displayTestTitle(this, TEST_NAME); - Task task = createTestTask(TEST_NAME); - OperationResult result = task.getResult(); + Task task = getTask(); + OperationResult result = getResult(); // GIVEN MockAsyncUpdateSource.INSTANCE.reset(); MockAsyncUpdateSource.INSTANCE.prepareMessage(getAmqp091Message(CHANGE_230)); + grouperDummyResource.getGroupByName(ALUMNI_NAME).addMember(NOBODY_USERNAME); // WHEN @@ -328,8 +546,7 @@ public void test230AddAlumniForNobody() throws Exception { // THEN - result.computeStatus(); - TestUtil.assertSuccess(result); + assertSuccess(result); assertMembers(ALUMNI_NAME, task, result, BANDERSON_USERNAME, JLEWIS685_USERNAME, NOBODY_USERNAME); } @@ -339,15 +556,14 @@ public void test230AddAlumniForNobody() throws Exception { */ @Test public void test250DeleteAlumniForAnderson() throws Exception { - final String TEST_NAME = "test250DeleteAlumniForAnderson"; - TestUtil.displayTestTitle(this, TEST_NAME); - Task task = createTestTask(TEST_NAME); - OperationResult result = task.getResult(); + Task task = getTask(); + OperationResult result = getResult(); // GIVEN MockAsyncUpdateSource.INSTANCE.reset(); MockAsyncUpdateSource.INSTANCE.prepareMessage(getAmqp091Message(CHANGE_250)); + grouperDummyResource.getGroupByName(ALUMNI_NAME).removeMember(BANDERSON_USERNAME); // WHEN @@ -357,20 +573,138 @@ public void test250DeleteAlumniForAnderson() throws Exception { // THEN - result.computeStatus(); - TestUtil.assertSuccess(result); + assertSuccess(result); - if (false) { // Disabled because of MID-5832 - assertMembers(ALUMNI_NAME, task, result, JLEWIS685_USERNAME, NOBODY_USERNAME); + assertMembers(ALUMNI_NAME, task, result, JLEWIS685_USERNAME, NOBODY_USERNAME); - assertUserAfterByUsername(BANDERSON_USERNAME) - .triggers() - .assertTriggers(1); - } + assertUserAfterByUsername(BANDERSON_USERNAME) + .triggers() + .assertTriggers(1); } - private Task createTestTask(String TEST_NAME) { - return createTask(TestGrouperAsyncUpdate.class.getName() + "." + TEST_NAME); + /** + * Anderson should lose the first assignment. + */ + @Test + public void test252RecomputeAnderson() throws Exception { + Task task = getTask(); + OperationResult result = getResult(); + + // WHEN + + recomputeUser(USER_BANDERSON.oid, task, result); + + // THEN + + assertSuccess(result); + + assertUserAfterByUsername(BANDERSON_USERNAME) + .assignments() + .assertAssignments(2) + .assertRole(ROLE_LDAP_BASIC.oid) + .assertOrg(orgStaffOid) + .end() + .links() + .assertLinks(1) + .projectionOnResource(resourceLdap.getOid()); + + openDJController.assertUniqueMember(DN_STAFF, DN_BANDERSON); + } + + /** + * Deleting ref:affiliation:staff group. + */ + @Test + public void test310DeleteStaff() throws Exception { + Task task = getTask(); + OperationResult result = getResult(); + + // GIVEN + + MockAsyncUpdateSource.INSTANCE.reset(); + MockAsyncUpdateSource.INSTANCE.prepareMessage(getAmqp091Message(CHANGE_310)); + grouperDummyResource.deleteGroupByName(STAFF_NAME); + + executeChanges(deltaFor(UserType.class).item(UserType.F_TRIGGER).replace().asObjectDelta(USER_BANDERSON.oid), null, task, result); + executeChanges(deltaFor(UserType.class).item(UserType.F_TRIGGER).replace().asObjectDelta(USER_JLEWIS685.oid), null, task, result); + + // WHEN + + ResourceShadowDiscriminator coords = new ResourceShadowDiscriminator(RESOURCE_GROUPER.oid); + String handle = provisioningService.startListeningForAsyncUpdates(coords, task, result); + provisioningService.stopListeningForAsyncUpdates(handle, task, result); + + // THEN + + assertSuccess(result); + + assertOrgByName("affiliation_staff", "staff after deletion") + .display() + .assertLifecycleState("retired") + .extension() + .property(EXT_GROUPER_NAME).singleValue().assertValue(STAFF_NAME).end().end() + .property(EXT_LDAP_DN).singleValue().assertValue(DN_STAFF).end().end() + .end() + .assertAssignments(1) // archetype, todo assert target + .assertDisplayName("Affiliation: staff") + .assertIdentifier("staff") + .links() + .projectionOnResource(RESOURCE_GROUPER.oid) + .target() + .assertDead() + .end() + .end() + .projectionOnResource(RESOURCE_LDAP.oid) + .target() + .assertNotDead() + .end() + .end() + .end(); + } + + /** + * Completes the deletion of staff group. + */ + @Test + public void test312ScavengeGroups() throws Exception { + Task task = getTask(); + OperationResult result = getResult(); + + // GIVEN + + + // WHEN + + rerunTask(TASK_GROUP_SCAVENGER.oid); + + // THEN + + assertSuccess(result); + + assertNoObject(OrgType.class, orgStaffOid, task, result); + assertUserAfterByUsername(BANDERSON_USERNAME) + .assignments() + .assertAssignments(1) + .assertRole(ROLE_LDAP_BASIC.oid) + .end() + .links() + .assertLinks(1) + .projectionOnResource(resourceLdap.getOid()); + + assertUserAfterByUsername(JLEWIS685_USERNAME) + .assignments() + .assertAssignments(2) + .assertRole(ROLE_LDAP_BASIC.oid) + .assertOrg(orgAlumniOid) + .end() + .links() + .assertLinks(1) + .projectionOnResource(resourceLdap.getOid()); + + openDJController.assertNoEntry(DN_STAFF); + + openDJController.assertNoUniqueMember(DN_ALUMNI, DN_BANDERSON); + openDJController.assertUniqueMember(DN_ALUMNI, DN_JLEWIS685); } private AsyncUpdateMessageType getAmqp091Message(File file) throws IOException { diff --git a/testing/story/src/test/resources/grouper/change-110-alumni-add.json b/testing/story/src/test/resources/grouper/change-110-alumni-add.json index ce14db8cd95..2f4eda80c7c 100644 --- a/testing/story/src/test/resources/grouper/change-110-alumni-add.json +++ b/testing/story/src/test/resources/grouper/change-110-alumni-add.json @@ -6,7 +6,7 @@ "changeOccurred": false, "createdOnMicros": 1551884850499000, "parentStemId": "9a7ce40af6c546148b41eec81b8ca18d", - "id": "00000000000000000000000000000002", + "id": "ref:affiliation:alumni", "sequenceNumber": "110", "eventType": "GROUP_ADD", "name": "ref:affiliation:alumni" diff --git a/testing/story/src/test/resources/grouper/change-115-staff-add.json b/testing/story/src/test/resources/grouper/change-115-staff-add.json new file mode 100644 index 00000000000..7dccbd60056 --- /dev/null +++ b/testing/story/src/test/resources/grouper/change-115-staff-add.json @@ -0,0 +1,15 @@ +{ + "encrypted": false, + "esbEvent": [ + { + "displayName": "ref:affiliation:staff", + "changeOccurred": false, + "createdOnMicros": 1551884850500000, + "parentStemId": "9a7ce40af6c546148b41eec81b8ca18d", + "id": "ref:affiliation:staff", + "sequenceNumber": "115", + "eventType": "GROUP_ADD", + "name": "ref:affiliation:staff" + } + ] +} \ No newline at end of file diff --git a/testing/story/src/test/resources/grouper/change-200-banderson-add-alumni.json b/testing/story/src/test/resources/grouper/change-200-banderson-add-alumni.json index bbb2a0203b2..a2fa43c85e9 100644 --- a/testing/story/src/test/resources/grouper/change-200-banderson-add-alumni.json +++ b/testing/story/src/test/resources/grouper/change-200-banderson-add-alumni.json @@ -5,7 +5,7 @@ "sourceId": "ldap", "membershipType": "flattened", "fieldName": "members", - "groupId": "00000000000000000000000000000002", + "groupId": "ref:affiliation:alumni", "changeOccurred": false, "createdOnMicros": 1551884899000000, "subjectId": "banderson", diff --git a/testing/story/src/test/resources/grouper/change-210-banderson-add-staff.json b/testing/story/src/test/resources/grouper/change-210-banderson-add-staff.json new file mode 100644 index 00000000000..3193d871ead --- /dev/null +++ b/testing/story/src/test/resources/grouper/change-210-banderson-add-staff.json @@ -0,0 +1,18 @@ +{ + "encrypted": false, + "esbEvent": [ + { + "sourceId": "ldap", + "membershipType": "flattened", + "fieldName": "members", + "groupId": "ref:affiliation:staff", + "changeOccurred": false, + "createdOnMicros": 1551884899000200, + "subjectId": "banderson", + "id": "35053020594395347781783127847322", + "sequenceNumber": "210", + "eventType": "MEMBERSHIP_ADD", + "groupName": "ref:affiliation:staff" + } + ] +} \ No newline at end of file diff --git a/testing/story/src/test/resources/grouper/change-220-jlewis685-add-alumni.json b/testing/story/src/test/resources/grouper/change-220-jlewis685-add-alumni.json index 3c5caa4d383..d672b48c732 100644 --- a/testing/story/src/test/resources/grouper/change-220-jlewis685-add-alumni.json +++ b/testing/story/src/test/resources/grouper/change-220-jlewis685-add-alumni.json @@ -5,7 +5,7 @@ "sourceId": "ldap", "membershipType": "flattened", "fieldName": "members", - "groupId": "00000000000000000000000000000002", + "groupId": "ref:affiliation:alumni", "changeOccurred": false, "createdOnMicros": 1551884899000200, "subjectId": "jlewis685", diff --git a/testing/story/src/test/resources/grouper/change-221-jlewis685-add-staff.json b/testing/story/src/test/resources/grouper/change-221-jlewis685-add-staff.json new file mode 100644 index 00000000000..77125ab3d98 --- /dev/null +++ b/testing/story/src/test/resources/grouper/change-221-jlewis685-add-staff.json @@ -0,0 +1,18 @@ +{ + "encrypted": false, + "esbEvent": [ + { + "sourceId": "ldap", + "membershipType": "flattened", + "fieldName": "members", + "groupId": "ref:affiliation:staff", + "changeOccurred": false, + "createdOnMicros": 1551884899000200, + "subjectId": "jlewis685", + "id": "32193201930193029105543238888888", + "sequenceNumber": "221", + "eventType": "MEMBERSHIP_ADD", + "groupName": "ref:affiliation:staff" + } + ] +} \ No newline at end of file diff --git a/testing/story/src/test/resources/grouper/change-230-nobody-add-alumni.json b/testing/story/src/test/resources/grouper/change-230-nobody-add-alumni.json index 920db3d1a88..74b79b10c0f 100644 --- a/testing/story/src/test/resources/grouper/change-230-nobody-add-alumni.json +++ b/testing/story/src/test/resources/grouper/change-230-nobody-add-alumni.json @@ -5,7 +5,7 @@ "sourceId": "ldap", "membershipType": "flattened", "fieldName": "members", - "groupId": "00000000000000000000000000000002", + "groupId": "ref:affiliation:alumni", "changeOccurred": false, "createdOnMicros": 1551884899000200, "subjectId": "nobody", diff --git a/testing/story/src/test/resources/grouper/change-250-banderson-delete-alumni.json b/testing/story/src/test/resources/grouper/change-250-banderson-delete-alumni.json index 8fc2043cdec..8361a9d543e 100644 --- a/testing/story/src/test/resources/grouper/change-250-banderson-delete-alumni.json +++ b/testing/story/src/test/resources/grouper/change-250-banderson-delete-alumni.json @@ -5,7 +5,7 @@ "sourceId": "ldap", "membershipType": "flattened", "fieldName": "members", - "groupId": "00000000000000000000000000000002", + "groupId": "ref:affiliation:alumni", "changeOccurred": false, "createdOnMicros": 1551884899000000, "subjectId": "banderson", diff --git a/testing/story/src/test/resources/grouper/change-310-staff-delete.json b/testing/story/src/test/resources/grouper/change-310-staff-delete.json new file mode 100644 index 00000000000..f7f7166c5db --- /dev/null +++ b/testing/story/src/test/resources/grouper/change-310-staff-delete.json @@ -0,0 +1,15 @@ +{ + "encrypted": false, + "esbEvent": [ + { + "displayName": "ref:staff", + "changeOccurred": false, + "createdOnMicros": 1551884850500000, + "parentStemId": "9a7ce40af6c546148b41eec81b8ca18d", + "id": "ref:affiliation:staff", + "sequenceNumber": "310", + "eventType": "GROUP_DELETE", + "name": "ref:affiliation:staff" + } + ] +} \ No newline at end of file diff --git a/testing/story/src/test/resources/grouper/function-library-grouper.xml b/testing/story/src/test/resources/grouper/function-library-grouper.xml index f62b9b899ac..282054b202c 100644 --- a/testing/story/src/test/resources/grouper/function-library-grouper.xml +++ b/testing/story/src/test/resources/grouper/function-library-grouper.xml @@ -85,7 +85,7 @@ import com.evolveum.midpoint.schema.constants.* import com.evolveum.midpoint.prism.delta.* - PLAIN_GROUP_OBJECT_CLASS = new ItemName(MidPointConstants.NS_RI, 'CustomPlainGroupObjectClass') + GROUP_OBJECT_CLASS = new ItemName(MidPointConstants.NS_RI, 'GroupObjectClass') TRIGGER_FIRE_AFTER = 60000 TRIGGER_SAFETY_MARGIN = 10000 @@ -125,7 +125,7 @@ ObjectDeltaType delta itemDelta = new ItemDeltaType() itemDelta.modificationType = eventType == 'MEMBERSHIP_ADD' ? ModificationTypeType.ADD : ModificationTypeType.DELETE - itemDelta.path = new ItemPathType(ItemPath.create(ShadowType.F_ATTRIBUTES, 'member')) + itemDelta.path = new ItemPathType(ItemPath.create(ShadowType.F_ATTRIBUTES, 'members')) itemDelta.value.add(RawType.fromPropertyRealValue(subjectId, null, prismContext)) delta = new ObjectDeltaType() delta.changeType = ChangeTypeType.MODIFY @@ -136,7 +136,7 @@ .createForNamedUser(subjectId) log.info('Recompute trigger for {}: {}', subjectId, added ? 'added' : 'not added (already present or user not found)') - return UcfChangeUtil.create(PLAIN_GROUP_OBJECT_CLASS, identifiers, delta, prismContext) + return UcfChangeUtil.create(GROUP_OBJECT_CLASS, identifiers, delta, prismContext) } else if (eventType == 'GROUP_ADD' || eventType == 'GROUP_DELETE') { groupName = esbEvent['name'] groupId = esbEvent['id'] @@ -155,7 +155,7 @@ } else { delta = null } - return UcfChangeUtil.create(PLAIN_GROUP_OBJECT_CLASS, identifiers, delta, prismContext) + return UcfChangeUtil.create(GROUP_OBJECT_CLASS, identifiers, delta, prismContext) } else { log.warn('Unsupported event type: {} -> {}', eventType, esbEvent) return null diff --git a/testing/story/src/test/resources/grouper/metarole-grouper-provided-group.xml b/testing/story/src/test/resources/grouper/metarole-grouper-provided-group.xml index 8d7322e558b..1274be7e518 100644 --- a/testing/story/src/test/resources/grouper/metarole-grouper-provided-group.xml +++ b/testing/story/src/test/resources/grouper/metarole-grouper-provided-group.xml @@ -148,6 +148,38 @@ displayName + + + lifecycle state + This mapping sets org lifecycle state to be either "active" or "retired", depending on + whether Grouper group for this org still exists. Orgs in the latter state are on the way to deletion: + their members are unassigned and after no members are there, the org is automatically deleted. + strong + + + + + lifecycleState + + - http://midpoint.evolveum.com/xml/ns/public/model/action-3#unlink - + + + true unlinked diff --git a/testing/story/src/test/resources/grouper/resource-ldap.xml b/testing/story/src/test/resources/grouper/resource-ldap.xml index 134463a1844..4b32a968a41 100644 --- a/testing/story/src/test/resources/grouper/resource-ldap.xml +++ b/testing/story/src/test/resources/grouper/resource-ldap.xml @@ -39,6 +39,7 @@ entryUUID ds-pwp-account-disabled isMemberOf + always false @@ -69,7 +70,7 @@ 0 false - mr:stringIgnoreCase + mr:distinguishedName strong @@ -192,7 +193,7 @@ ri:dn - mr:stringIgnoreCase + mr:distinguishedName strong @@ -210,6 +211,11 @@ + + ri:uniqueMember + mr:distinguishedName + minimal + @@ -304,6 +310,6 @@ - true + false diff --git a/testing/story/src/test/resources/grouper/system-configuration.xml b/testing/story/src/test/resources/grouper/system-configuration.xml index f6775969246..743b27df3a6 100644 --- a/testing/story/src/test/resources/grouper/system-configuration.xml +++ b/testing/story/src/test/resources/grouper/system-configuration.xml @@ -31,6 +31,15 @@ UserType + + OrgType + + + retired + + + + diff --git a/testing/story/src/test/resources/grouper/task-group-scavenger.xml b/testing/story/src/test/resources/grouper/task-group-scavenger.xml new file mode 100644 index 00000000000..549fb0c9b1d --- /dev/null +++ b/testing/story/src/test/resources/grouper/task-group-scavenger.xml @@ -0,0 +1,83 @@ + + + + + + + Group Scavenger + + + + execute-script + + script + + import com.evolveum.midpoint.xml.ns._public.common.common_3.* + + result = midpoint.currentResult + log.info('Processing dead group: {}', input) + query = prismContext.queryFor(UserType.class) + .item(UserType.F_ROLE_MEMBERSHIP_REF).ref(input.oid) + .build() + members = midpoint.repositoryService.searchObjects(UserType.class, query, null, result) + log.info('Found {} members: {}', members.size(), members) + + for (member in members) { + log.info('Going to recompute {}', member) + try { + midpoint.recompute(UserType.class, member.oid) + } catch (Throwable t) { + log.error('Couldn\'t recompute {}: {}', member, t.message, t) + } + } + log.info('Members recomputed; checking if the org is still in "retired" state') + orgAfter = midpoint.repositoryService.getObject(OrgType.class, input.oid, null, result) + currentState = orgAfter.asObjectable().lifecycleState + log.info('Current state = {}', currentState) + if (currentState == 'retired') { + log.info('Deleting the org: {}', orgAfter) + midpoint.deleteObject(OrgType.class, orgAfter.oid, null) + } else { + log.info('State has changed, not deleting the org: {}', orgAfter) + } + log.info('Dead group processing done: {}', input) + + + + + + OrgType + + + + lifecycleState + retired + + + + + + runnable + BulkActions + http://midpoint.evolveum.com/xml/ns/public/model/iterative-scripting/handler-3 + recurring + + diff --git a/testing/story/src/test/resources/grouper/template-user.xml b/testing/story/src/test/resources/grouper/template-user.xml index 73476790beb..5336a9c8be8 100644 --- a/testing/story/src/test/resources/grouper/template-user.xml +++ b/testing/story/src/test/resources/grouper/template-user.xml @@ -24,17 +24,17 @@ import com.evolveum.midpoint.prism.path.* GROUPER_RESOURCE_OID = '1eff65de-5bb6-483d-9edf-8cc2c2ee0233' - MEMBER_NAME = new QName(MidPointConstants.NS_RI, 'member') + MEMBER_NAME = new QName(MidPointConstants.NS_RI, 'members') memberDef = prismContext.definitionFactory().createPropertyDefinition(MEMBER_NAME, DOMUtil.XSD_STRING) memberDef.setMaxOccurs(-1) - - // TODO check for exists/dead + shadowQuery = prismContext.queryFor(ShadowType.class) .item(ShadowType.F_RESOURCE_REF).ref(GROUPER_RESOURCE_OID) .and().item(ShadowType.F_SYNCHRONIZATION_SITUATION).eq(SynchronizationSituationType.LINKED) .and().item(ShadowType.F_KIND).eq(ShadowKindType.ENTITLEMENT) .and().item(ShadowType.F_INTENT).eq('group') + .and().block().item(ShadowType.F_DEAD).isNull().or().item(ShadowType.F_DEAD).eq(false).endBlock() .and().item(ItemPath.create(ShadowType.F_ATTRIBUTES, MEMBER_NAME), memberDef).eq(basic.stringify(name)) .build() diff --git a/testing/story/src/test/resources/inbound-outbound-association/resource-dummy-dir.xml b/testing/story/src/test/resources/inbound-outbound-association/resource-dummy-dir.xml index d6ae206a0fd..3fb7ee53b6b 100644 --- a/testing/story/src/test/resources/inbound-outbound-association/resource-dummy-dir.xml +++ b/testing/story/src/test/resources/inbound-outbound-association/resource-dummy-dir.xml @@ -92,7 +92,6 @@ true true - false strong @@ -123,21 +122,23 @@ import com.evolveum.midpoint.schema.constants.* import com.evolveum.midpoint.xml.ns._public.common.common_3.RoleType; - if (assignment.target != null) { - log.info("### (association) target roleType " + assignment.target.roleType) - inRange = 'group'.equals(assignment.target.subtype) - log.info("########## (association) inRange: " + inRange) - return inRange - } - if (assignment.targetRef != null) { - role = midpoint.getObject(RoleType.class, assignment.targetRef.oid) - log.info("### (association) role name " + role.name.orig) - log.info("### (association) role.roleType " + role.subtype) - inRange = ('group').equals(role.subtype) - log.info("########## (association) inRange: " + inRange) - return inRange + if (assignment.targetRef.object != null) { + log.info("### (association) target roleType " + assignment.targetRef.objectable.roleType) + inRange = 'group'.equals(assignment.targetRef.objectable..subtype) + log.info("########## (association) inRange: " + inRange) + return inRange + } else { + role = midpoint.getObject(RoleType.class, assignment.targetRef.oid) + log.info("### (association) role name " + role.name.orig) + log.info("### (association) role.roleType " + role.subtype) + inRange = ('group').equals(role.subtype) + log.info("########## (association) inRange: " + inRange) + return inRange + } } + + return false diff --git a/testing/story/src/test/resources/plenty-of-assignments/resource-dummy.xml b/testing/story/src/test/resources/plenty-of-assignments/resource-dummy.xml index f39fcbceb63..e9378410b51 100644 --- a/testing/story/src/test/resources/plenty-of-assignments/resource-dummy.xml +++ b/testing/story/src/test/resources/plenty-of-assignments/resource-dummy.xml @@ -107,8 +107,8 @@ Ship + false diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/assignmentholder/AssignmentHolderObjectListPage.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/assignmentholder/AssignmentHolderObjectListPage.java index 149c39eec92..c0b0d6ba8e1 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/assignmentholder/AssignmentHolderObjectListPage.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/assignmentholder/AssignmentHolderObjectListPage.java @@ -10,6 +10,7 @@ import com.codeborne.selenide.SelenideElement; import com.evolveum.midpoint.schrodinger.MidPoint; import com.evolveum.midpoint.schrodinger.page.BasicPage; +import org.apache.commons.lang3.StringUtils; import org.openqa.selenium.By; import static com.codeborne.selenide.Selenide.$; @@ -22,9 +23,19 @@ public abstract class AssignmentHolderObjectListPage showEmptyAttributes(String containerName) { public Boolean compareInputAttributeValue(String name, String expectedValue) { SelenideElement property = findProperty(name); - SelenideElement value = property.$(By.xpath(".//input[contains(@class,\"form-control\")]")); + SelenideElement value = property.parent().$(By.xpath(".//input[contains(@class,\"form-control\")]")); String valueElement = value.getValue(); if (!valueElement.isEmpty()) { @@ -274,7 +274,8 @@ public PrismForm addNewContainerValue(String containerHeaderKey, String newCo SelenideElement panelHeader = $(By.linkText(containerHeaderKey)) .parent() .parent(); - panelHeader.$(Schrodinger.byDataId("addButton")) + panelHeader.scrollTo(); + panelHeader.find(By.className("fa-plus-circle")) .waitUntil(Condition.visible, MidPoint.TIMEOUT_DEFAULT_2_S) .click(); @@ -291,11 +292,12 @@ public PrismForm addNewContainerValue(String containerHeaderKey, String newCo public SelenideElement getPrismPropertiesPanel(String containerHeaderKey){ expandContainerPropertiesPanel(containerHeaderKey); - SelenideElement containerHeaderPanel = $(Schrodinger.byDataResourceKey("div", containerHeaderKey)); + SelenideElement containerHeaderPanel = $(Schrodinger.byDataResourceKey("a", containerHeaderKey)); return containerHeaderPanel .parent() - .$(By.className("prism-properties")) - .shouldBe(Condition.visible) + .parent() + .parent() + .find(By.className("prism-properties")) .waitUntil(Condition.visible, MidPoint.TIMEOUT_DEFAULT_2_S); } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/table/Table.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/table/Table.java index e5412e721e6..92a8f4bde0c 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/table/Table.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/component/common/table/Table.java @@ -17,6 +17,7 @@ import org.openqa.selenium.By; +import static com.codeborne.selenide.Selectors.byPartialLinkText; import static com.codeborne.selenide.Selectors.byText; import static com.codeborne.selenide.Selenide.$; @@ -69,6 +70,10 @@ public boolean containsText(String value){ return $(byText(value)).waitUntil(Condition.visible, MidPoint.TIMEOUT_MEDIUM_6_S).is(Condition.visible); } + public boolean containsLinkTextPartially(String value){ + return $(byPartialLinkText(value)).waitUntil(Condition.visible, MidPoint.TIMEOUT_MEDIUM_6_S).is(Condition.visible); + } + public boolean buttonToolBarExists(){ return $(Schrodinger.byDataId("buttonToolbar")).exists(); } diff --git a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/user/ListUsersPage.java b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/user/ListUsersPage.java index c3702acb3af..6fa9690aa4f 100644 --- a/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/user/ListUsersPage.java +++ b/tools/schrodinger/src/main/java/com/evolveum/midpoint/schrodinger/page/user/ListUsersPage.java @@ -13,6 +13,7 @@ import com.evolveum.midpoint.schrodinger.component.common.FeedbackBox; import com.evolveum.midpoint.schrodinger.component.user.UsersPageTable; import com.evolveum.midpoint.schrodinger.page.BasicPage; +import com.evolveum.midpoint.schrodinger.util.ConstantsUtil; import org.openqa.selenium.By; import static com.codeborne.selenide.Selenide.$; @@ -26,4 +27,10 @@ public class ListUsersPage extends AssignmentHolderObjectListPage