From 22a3eb37aa0cf43c948030a281a42b8cbf07f226 Mon Sep 17 00:00:00 2001 From: Radovan Semancik Date: Tue, 31 Jul 2018 18:13:00 +0200 Subject: [PATCH] Consistency update: fixing tests, required improvements, stabilisation (MID-3891) --- .../midpoint/schema/GetOperationOptions.java | 37 +++ .../midpoint/schema/util/ShadowUtil.java | 2 + .../handlers/BaseCertificationHandler.java | 3 +- .../model/impl/lens/ChangeExecutor.java | 59 +++-- .../midpoint/model/impl/lens/Clockwork.java | 3 + .../impl/lens/LensProjectionContext.java | 6 + .../model/impl/lens/PersonaProcessor.java | 1 + .../impl/lens/projector/ContextLoader.java | 245 +++++++++--------- .../lens/projector/DependencyProcessor.java | 57 ++-- .../projector/focus/AssignmentProcessor.java | 1 + .../impl/security/UserProfileServiceImpl.java | 2 +- .../lens/TestAbstractAssignmentEvaluator.java | 2 + .../impl/lens/TestAssignmentProcessor2.java | 3 +- .../model/intest/TestMultiResource.java | 235 ++++++++++++----- .../src/test/resources/logback-test.xml | 18 +- .../resources/multi/role-dummies-beige.xml | 6 +- .../test/AbstractModelIntegrationTest.java | 42 +++ .../report/impl/ReportCreateTaskHandler.java | 3 +- .../report/impl/ReportServiceImpl.java | 3 +- .../provisioning/api/ProvisioningService.java | 8 +- .../impl/ProvisioningServiceImpl.java | 8 +- .../provisioning/impl/ShadowCache.java | 20 +- .../provisioning/impl/ShadowManager.java | 17 +- .../impl/dummy/TestDummyConsistency.java | 175 ++++++++++++- .../test/AbstractIntegrationTest.java | 13 +- .../test/asserter/AbstractAsserter.java | 34 +++ .../midpoint/test/asserter/FocusAsserter.java | 210 +++++++++++++++ .../test/asserter/ObjectDeltaAsserter.java | 2 +- .../asserter/ObjectDeltaTypeAsserter.java | 2 +- .../asserter/ObjectReferenceAsserter.java | 118 +++++++++ .../asserter/PendingOperationAsserter.java | 2 +- .../asserter/PendingOperationsAsserter.java | 27 +- .../test/asserter/PrismObjectAsserter.java | 10 + .../test/asserter/ShadowAsserter.java | 14 +- .../asserter/ShadowAttributesAsserter.java | 2 +- .../asserter/ShadowReferenceAsserter.java | 85 ++++++ .../midpoint/test/asserter/UserAsserter.java | 162 ++++++++++++ .../main/resources/ctx-configuration-test.xml | 2 + 38 files changed, 1359 insertions(+), 280 deletions(-) create mode 100644 repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/FocusAsserter.java create mode 100644 repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ObjectReferenceAsserter.java create mode 100644 repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ShadowReferenceAsserter.java create mode 100644 repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/UserAsserter.java diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/GetOperationOptions.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/GetOperationOptions.java index 4c7be7228d4..26e2c0c3441 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/GetOperationOptions.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/GetOperationOptions.java @@ -135,8 +135,21 @@ public class GetOperationOptions extends AbstractOptions implements Serializable * The default value is zero, which means that a fresh value must always be returned. This means that caches that do * not guarantee fresh value cannot be used. If non-zero value is specified then such caches may be used. In case that * Long.MAX_VALUE is specified then the caches are always used and fresh value is never retrieved. + * + * Null value is special in one more aspect: it allows to return partial cached data in case that the original is not + * accessible. E.g. in that case provisioning can return repository shadow in case that the resource is not reachable. + * Explicit specification of staleness=0 disables this behavior. */ private Long staleness; + + /** + * Force refresh of object before the data are retrieved. This option is a guarantee that we get the freshest + * data that is possible. However, strange things may happen here. E.g. object that existed before this operation + * may get deleted during refresh because it has expired in the meantime. Or get operation may in fact attempt + * to create, modify and even delete of an account. This may happen in case that there are some unfinished + * operations in the shadow. Therefore when using this option you have to be really prepared for everything. + */ + private Boolean forceRefresh; /** * Should the results be made distinct. @@ -650,6 +663,30 @@ public static long getStaleness(GetOperationOptions options) { public static boolean isMaxStaleness(GetOperationOptions options) { return GetOperationOptions.getStaleness(options) == Long.MAX_VALUE; } + + public Boolean getForceRefresh() { + return forceRefresh; + } + + public void setForceRefresh(Boolean forceRefresh) { + this.forceRefresh = forceRefresh; + } + + public static boolean isForceRefresh(GetOperationOptions options) { + if (options == null) { + return false; + } + if (options.forceRefresh == null) { + return false; + } + return options.forceRefresh; + } + + public static GetOperationOptions createForceRefresh() { + GetOperationOptions opts = new GetOperationOptions(); + opts.setForceRefresh(true); + return opts; + } public Boolean getDistinct() { return distinct; diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ShadowUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ShadowUtil.java index 33e5b5f48e1..c0759b18f64 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ShadowUtil.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ShadowUtil.java @@ -31,6 +31,8 @@ import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.ChangeTypeType; +import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; import javax.xml.datatype.XMLGregorianCalendar; diff --git a/model/certification-impl/src/main/java/com/evolveum/midpoint/certification/impl/handlers/BaseCertificationHandler.java b/model/certification-impl/src/main/java/com/evolveum/midpoint/certification/impl/handlers/BaseCertificationHandler.java index dc878b1f2d1..ac54df610e6 100644 --- a/model/certification-impl/src/main/java/com/evolveum/midpoint/certification/impl/handlers/BaseCertificationHandler.java +++ b/model/certification-impl/src/main/java/com/evolveum/midpoint/certification/impl/handlers/BaseCertificationHandler.java @@ -38,6 +38,7 @@ import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import javax.xml.namespace.QName; @@ -54,7 +55,7 @@ public abstract class BaseCertificationHandler implements CertificationHandler { @Autowired protected PrismContext prismContext; @Autowired protected ModelService modelService; - @Autowired protected ObjectResolver objectResolver; + @Autowired @Qualifier("modelObjectResolver") protected ObjectResolver objectResolver; @Autowired protected CertificationManagerImpl certificationManager; @Autowired protected AccCertGeneralHelper helper; @Autowired protected AccCertResponseComputationHelper computationHelper; diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java index 3bb7b3a9251..ae33673d9c5 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/ChangeExecutor.java @@ -251,6 +251,7 @@ public boolean executeChanges(LensContext context, Tas subResult.addParam("resource", projCtx.getResource()); } + PrismObject shadowAfterModification = null; try { LOGGER.trace("Executing projection context {}", projCtx.toHumanReadableString()); @@ -281,7 +282,7 @@ public boolean executeChanges(LensContext context, Tas } if (projDelta != null && projDelta.isDelete()) { - executeDelta(projDelta, projCtx, context, null, null, projCtx.getResource(), task, + shadowAfterModification = executeDelta(projDelta, projCtx, context, null, null, projCtx.getResource(), task, subResult); } @@ -291,8 +292,9 @@ public boolean executeChanges(LensContext context, Tas if (LOGGER.isTraceEnabled()) { LOGGER.trace("No change for " + projCtx.getResourceShadowDiscriminator()); } + shadowAfterModification = projCtx.getObjectCurrent(); if (focusContext != null) { - updateLinks(focusContext, projCtx, task, subResult); + updateLinks(focusContext, projCtx, shadowAfterModification, task, subResult); } // Make sure post-reconcile delta is always executed, @@ -317,13 +319,13 @@ public boolean executeChanges(LensContext context, Tas } } - executeDelta(projDelta, projCtx, context, null, null, projCtx.getResource(), task, subResult); + shadowAfterModification = executeDelta(projDelta, projCtx, context, null, null, projCtx.getResource(), task, subResult); } subResult.computeStatus(); if (focusContext != null) { - updateLinks(focusContext, projCtx, task, subResult); + updateLinks(focusContext, projCtx, shadowAfterModification, task, subResult); } executeReconciliationScript(projCtx, context, BeforeAfterType.AFTER, task, subResult); @@ -338,7 +340,7 @@ public boolean executeChanges(LensContext context, Tas // We still want to update the links here. E.g. this may be live sync case where we discovered new account // try to reconcile, but the reconciliation fails. We still want this shadow linked to user. if (focusContext != null) { - updateLinks(focusContext, projCtx, task, subResult); + updateLinks(focusContext, projCtx, shadowAfterModification, task, subResult); } } catch (ObjectAlreadyExistsException e) { @@ -590,8 +592,9 @@ private void recordFatalError(OperationResult subResult, OperationResult result, * Make sure that the account is linked (or unlinked) as needed. */ private void updateLinks( - LensFocusContext focusObjectContext, LensProjectionContext projCtx, Task task, - OperationResult result) throws ObjectNotFoundException, SchemaException { + LensFocusContext focusObjectContext, LensProjectionContext projCtx, + PrismObject shadowAfterModification, + Task task, OperationResult result) throws ObjectNotFoundException, SchemaException { if (focusObjectContext == null) { return; } @@ -619,7 +622,7 @@ private void updateLinks( throw new IllegalStateException("Projection "+projCtx.toHumanReadableString()+" has null OID, this should not happen"); } - if (linkShouldExist(projCtx, result)) { + if (linkShouldExist(projCtx, shadowAfterModification, result)) { // Link should exist PrismObject objectCurrent = focusContext.getObjectCurrent(); @@ -686,24 +689,19 @@ private void updateLinks( } } - private boolean linkShouldExist(LensProjectionContext projCtx, OperationResult result) { + private boolean linkShouldExist(LensProjectionContext projCtx, PrismObject shadowAfterModification, OperationResult result) { if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.UNLINK) { return false; } if (isEmptyThombstone(projCtx)) { return false; } - if (projCtx.hasPendingOperations()) { - return true; - } if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.DELETE || projCtx.isDelete()) { - if (result.isInProgress()) { - // Keep the link until operation is finished - return true; - } else { - return false; - } + return shadowAfterModification != null; + } + if (projCtx.hasPendingOperations()) { + return true; } return true; } @@ -866,7 +864,11 @@ private void updateSituationInShadow(Task task, } - private void executeDelta(ObjectDelta objectDelta, + /** + * @return Returns estimate of the object after modification. Or null if the object was deleted. + * NOTE: this is only partially implemented (only for shadow delete). + */ + private PrismObject executeDelta(ObjectDelta objectDelta, LensElementContext objectContext, LensContext context, ModelExecuteOptions options, ConflictResolutionType conflictResolution, ResourceType resource, Task task, OperationResult parentResult) throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException, @@ -889,7 +891,7 @@ private void executeDelta(ObjectDel if (objectDelta == null || objectDelta.isEmpty()) { LOGGER.debug("Skipping execution of delta because it was already executed: {}", objectContext); - return; + return objectContext.getObjectCurrent(); } if (InternalsConfig.consistencyChecks) { @@ -909,6 +911,7 @@ private void executeDelta(ObjectDel } OperationResult result = parentResult.createSubresult(OPERATION_EXECUTE_DELTA); + PrismObject objectAfterModification = null; try { @@ -917,7 +920,7 @@ private void executeDelta(ObjectDel } else if (objectDelta.getChangeType() == ChangeType.MODIFY) { executeModification(objectDelta, context, objectContext, options, conflictResolution, resource, task, result); } else if (objectDelta.getChangeType() == ChangeType.DELETE) { - executeDeletion(objectDelta, context, objectContext, options, resource, task, result); + objectAfterModification = executeDeletion(objectDelta, context, objectContext, options, resource, task, result); } // To make sure that the OID is set (e.g. after ADD operation) @@ -947,6 +950,8 @@ private void executeDelta(ObjectDel } } } + + return objectAfterModification; } private void removeExecutedItemDeltas( @@ -1274,7 +1279,7 @@ private void executeAddition(Object } } - private void executeDeletion(ObjectDelta change, + private PrismObject executeDeletion(ObjectDelta change, LensContext context, LensElementContext objectContext, ModelExecuteOptions options, ResourceType resource, Task task, OperationResult result) throws ObjectNotFoundException, ObjectAlreadyExistsException, SchemaException, CommunicationException, @@ -1285,6 +1290,7 @@ private void executeDeletion(Object PrismObject objectOld = objectContext.getObjectOld(); OwnerResolver ownerResolver = createOwnerResolver(context, task, result); + PrismObject objectAfterModification = null; try { securityEnforcer.authorize(ModelAuthorizationAction.DELETE.getUrl(), AuthorizationPhaseType.EXECUTION, AuthorizationParameters.Builder.buildObject(objectOld), ownerResolver, task, result); @@ -1296,7 +1302,7 @@ private void executeDeletion(Object } else if (ObjectTypes.isClassManagedByProvisioning(objectTypeClass)) { ProvisioningOperationOptions provisioningOptions = getProvisioningOptions(context, options); try { - deleteProvisioningObject(objectTypeClass, oid, context, objectContext, + objectAfterModification = deleteProvisioningObject(objectTypeClass, oid, context, objectContext, provisioningOptions, resource, task, result); } catch (ObjectNotFoundException e) { // Object that we wanted to delete is already gone. This can @@ -1327,6 +1333,8 @@ private void executeDeletion(Object context.getChannel(), t); throw t; } + + return objectAfterModification; } private void executeModification(ObjectDelta delta, @@ -1428,7 +1436,7 @@ private String addProvisioningObjec return oid; } - private void deleteProvisioningObject( + private PrismObject deleteProvisioningObject( Class objectTypeClass, String oid, LensContext context, LensElementContext objectContext, ProvisioningOperationOptions options, ResourceType resource, Task task, OperationResult result) throws ObjectNotFoundException, ObjectAlreadyExistsException, SchemaException, @@ -1452,8 +1460,9 @@ private void deleteProvisioningObje ProvisioningOperationTypeType.DELETE, resource, task, result); } Utils.setRequestee(task, context); - provisioning.deleteObject(objectTypeClass, oid, options, scripts, task, result); + PrismObject objectAfterModification = provisioning.deleteObject(objectTypeClass, oid, options, scripts, task, result); Utils.clearRequestee(task); + return objectAfterModification; } private String modifyProvisioningObject( 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 dc5956dde84..e45aaf80b98 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 @@ -1283,6 +1283,9 @@ private void logFinalReadable(LensContext context, Tas for (LensProjectionContext projectionContext: context.getProjectionContexts()) { DebugUtil.indentDebugDump(sb, 1); sb.append(projectionContext.getHumanReadableName()); + if (projectionContext.isThombstone()) { + sb.append(" THOMBSTONE"); + } sb.append(": "); sb.append(projectionContext.getSynchronizationPolicyDecision()); sb.append("\n"); diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java index 9492ffb9a98..aeb42642356 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/LensProjectionContext.java @@ -112,6 +112,12 @@ public class LensProjectionContext extends LensElementContext implem private Boolean isLegal = null; private Boolean isLegalOld = null; + /** + * True if the projection exists (or will exist) on resource. False if it does not exist. + * NOTE: entire projection is loaded with pointInTime=future. Therefore this does NOT + * reflect actual situation. If there is a pending operation to create the object then + * isExists will in fact be true. + */ private boolean isExists; /** diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/PersonaProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/PersonaProcessor.java index df09c5a270d..ad17a412cc5 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/PersonaProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/PersonaProcessor.java @@ -87,6 +87,7 @@ public class PersonaProcessor { private ObjectTemplateProcessor objectTemplateProcessor; @Autowired(required=true) + @Qualifier("modelObjectResolver") private ObjectResolver objectResolver; @Autowired 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 9975e65445f..663f248664b 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 @@ -543,18 +543,22 @@ private void loadLinkRefsFromFocus(LensContext context, // Make sure it has a proper definition. This may come from outside of the model. provisioningService.applyDefinition(shadow, task, result); } - LensProjectionContext accountContext = getOrCreateAccountContext(context, shadow, task, result); - accountContext.setFresh(true); - accountContext.setExists(shadow != null); + LensProjectionContext projectionContext = getOrCreateAccountContext(context, shadow, task, result); + projectionContext.setFresh(true); + if (shadow == null) { + projectionContext.setExists(false); + } else { + projectionContext.setExists(ShadowUtil.isExists(shadow.asObjectable())); + } if (context.isDoReconciliationForAllProjections()) { - accountContext.setDoReconciliation(true); + projectionContext.setDoReconciliation(true); } - if (accountContext.isDoReconciliation()) { + if (projectionContext.isDoReconciliation()) { // Do not load old account now. It will get loaded later in the // reconciliation step. continue; } - accountContext.setLoadedObject(shadow); + projectionContext.setLoadedObject(shadow); } } @@ -596,7 +600,7 @@ private void loadLinkRefsFromDelta(LensContext context, if (linkRefDelta.getValuesToAdd() != null) { for (PrismReferenceValue refVal : linkRefDelta.getValuesToAdd()) { String oid = refVal.getOid(); - LensProjectionContext accountContext = null; + LensProjectionContext projectionContext = null; PrismObject shadow = null; boolean isCombinedAdd = false; if (oid == null) { @@ -609,15 +613,15 @@ private void loadLinkRefsFromDelta(LensContext context, provisioningService.applyDefinition(shadow, task, result); if (consistencyChecks) ShadowUtil.checkConsistence(shadow, "account from "+linkRefDelta); // Check for conflicting change - accountContext = LensUtil.getProjectionContext(context, shadow, provisioningService, prismContext, task, result); - if (accountContext != null) { + projectionContext = LensUtil.getProjectionContext(context, shadow, provisioningService, prismContext, task, result); + if (projectionContext != null) { // There is already existing context for the same discriminator. Tolerate this only if // the deltas match. It is an error otherwise. - ObjectDelta primaryDelta = accountContext.getPrimaryDelta(); + ObjectDelta primaryDelta = projectionContext.getPrimaryDelta(); if (primaryDelta == null) { throw new SchemaException("Attempt to add "+shadow+" to a user that already contains "+ - accountContext.getHumanReadableKind()+" of type '"+ - accountContext.getResourceShadowDiscriminator().getIntent()+"' on "+accountContext.getResource()); + projectionContext.getHumanReadableKind()+" of type '"+ + projectionContext.getResourceShadowDiscriminator().getIntent()+"' on "+projectionContext.getResource()); } if (!primaryDelta.isAdd()) { throw new SchemaException("Conflicting changes in the context. " + @@ -629,14 +633,14 @@ private void loadLinkRefsFromDelta(LensContext context, } } else { // Create account context from embedded object - accountContext = createProjectionContext(context, shadow, task, result); + projectionContext = createProjectionContext(context, shadow, task, result); } // This is a new account that is to be added. So it should // go to account primary delta ObjectDelta accountPrimaryDelta = shadow.createAddDelta(); - accountContext.setPrimaryDelta(accountPrimaryDelta); - accountContext.setFullShadow(true); - accountContext.setExists(false); + projectionContext.setPrimaryDelta(accountPrimaryDelta); + projectionContext.setFullShadow(true); + projectionContext.setExists(false); isCombinedAdd = true; } else { // We have OID. This is either linking of existing account or @@ -650,9 +654,9 @@ private void loadLinkRefsFromDelta(LensContext context, Collection> options = SelectorOptions.createCollection(rootOpts); shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); // Create account context from retrieved object - accountContext = getOrCreateAccountContext(context, shadow, task, result); - accountContext.setLoadedObject(shadow); - accountContext.setExists(true); + projectionContext = getOrCreateAccountContext(context, shadow, task, result); + projectionContext.setLoadedObject(shadow); + projectionContext.setExists(ShadowUtil.isExists(shadow.asObjectable())); } catch (ObjectNotFoundException e) { if (refVal.getObject() == null) { // account does not exist, no composite account in @@ -666,27 +670,27 @@ private void loadLinkRefsFromDelta(LensContext context, provisioningService.applyDefinition(shadow, task, result); } // Create account context from embedded object - accountContext = createProjectionContext(context, shadow, task, result); + projectionContext = createProjectionContext(context, shadow, task, result); ObjectDelta accountPrimaryDelta = shadow.createAddDelta(); - accountContext.setPrimaryDelta(accountPrimaryDelta); - accountContext.setFullShadow(true); - accountContext.setExists(false); + projectionContext.setPrimaryDelta(accountPrimaryDelta); + projectionContext.setFullShadow(true); + projectionContext.setExists(false); isCombinedAdd = true; } } } if (context.isDoReconciliationForAllProjections() && !isCombinedAdd) { - accountContext.setDoReconciliation(true); + projectionContext.setDoReconciliation(true); } - accountContext.setFresh(true); + projectionContext.setFresh(true); } } if (linkRefDelta.getValuesToDelete() != null) { for (PrismReferenceValue refVal : linkRefDelta.getValuesToDelete()) { String oid = refVal.getOid(); - LensProjectionContext accountContext = null; - PrismObject account = null; + LensProjectionContext projectionContext = null; + PrismObject shadow = null; if (oid == null) { throw new SchemaException("Cannot delete account ref without an oid in " + focus); } else { @@ -694,19 +698,19 @@ private void loadLinkRefsFromDelta(LensContext context, // Using NO_FETCH so we avoid reading in a full account. This is more efficient as we don't need full account here. // We need to fetch from provisioning and not repository so the correct definition will be set. Collection> options = SelectorOptions.createCollection(GetOperationOptions.createNoFetch()); - account = provisioningService.getObject(ShadowType.class, oid, options, task, result); + shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); // Create account context from retrieved object - accountContext = getOrCreateAccountContext(context, account, task, result); - accountContext.setLoadedObject(account); - accountContext.setExists(true); + projectionContext = getOrCreateAccountContext(context, shadow, task, result); + projectionContext.setLoadedObject(shadow); + projectionContext.setExists(ShadowUtil.isExists(shadow.asObjectable())); } catch (ObjectNotFoundException e) { try{ // Broken accountRef. We need to try again with raw options, because the error should be thrown because of non-existent resource Collection> options = SelectorOptions.createCollection(GetOperationOptions.createRaw()); - account = provisioningService.getObject(ShadowType.class, oid, options, task, result); - accountContext = getOrCreateEmptyThombstoneProjectionContext(context, oid); - accountContext.setFresh(true); - accountContext.setExists(false); + shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result); + projectionContext = getOrCreateEmptyThombstoneProjectionContext(context, oid); + projectionContext.setFresh(true); + projectionContext.setExists(false); OperationResult getObjectSubresult = result.getLastSubresult(); getObjectSubresult.setErrorsHandled(); } catch (ObjectNotFoundException ex){ @@ -719,15 +723,15 @@ private void loadLinkRefsFromDelta(LensContext context, } } - if (accountContext != null) { + if (projectionContext != null) { if (refVal.getObject() == null) { - accountContext.setSynchronizationIntent(SynchronizationIntent.UNLINK); + projectionContext.setSynchronizationIntent(SynchronizationIntent.UNLINK); } else { - accountContext.setSynchronizationIntent(SynchronizationIntent.DELETE); - ObjectDelta accountPrimaryDelta = account.createDeleteDelta(); - accountContext.setPrimaryDelta(accountPrimaryDelta); + projectionContext.setSynchronizationIntent(SynchronizationIntent.DELETE); + ObjectDelta accountPrimaryDelta = shadow.createDeleteDelta(); + projectionContext.setPrimaryDelta(accountPrimaryDelta); } - accountContext.setFresh(true); + projectionContext.setFresh(true); } } @@ -769,7 +773,7 @@ private void loadProjectionContextsSync(LensContext co if (syncDelta.getChangeType() == ChangeType.ADD) { shadow = syncDelta.getObjectToAdd().clone(); projCtx.setLoadedObject(shadow); - projCtx.setExists(true); + projCtx.setExists(ShadowUtil.isExists(shadow.asObjectable())); } else { @@ -803,7 +807,7 @@ private void loadProjectionContextsSync(LensContext co } else if (shadow != null) { syncDelta.applyTo(shadow); projCtx.setLoadedObject(shadow); - projCtx.setExists(true); + projCtx.setExists(ShadowUtil.isExists(shadow.asObjectable())); } } @@ -1019,9 +1023,9 @@ private void finishLoadOfProjectionContext(LensContext "Projection "+projectionHumanReadableName+" with null OID, no representation and no resource OID in account sync context "+projContext); } } else { - projContext.setExists(true); GetOperationOptions rootOptions = GetOperationOptions.createPointInTimeType(PointInTimeType.FUTURE); if (projContext.isDoReconciliation()) { + rootOptions.setForceRefresh(true); if (SchemaConstants.CHANGE_CHANNEL_DISCOVERY_URI.equals(context.getChannel())) { // Avoid discovery loops rootOptions.setDoNotDiscovery(true); @@ -1061,15 +1065,16 @@ private void finishLoadOfProjectionContext(LensContext } else { projContext.setFullShadow(false); } + projContext.setExists(ShadowUtil.isExists(objectOld.asObjectable())); projectionObject = objectOld; } catch (ObjectNotFoundException ex) { - // This does not mean BROKEN. The projection was there, but it gone now. What we really want here - // is a thombstone projection. - thombstone = true; - projContext.setFullShadow(false); - LOGGER.warn("Could not find object with oid {}. The projection context {} is marked as thombstone.", projectionObjectOid, projectionHumanReadableName); - + LOGGER.debug("Could not find object with oid {} for projection context {}.", projectionObjectOid, projectionHumanReadableName); + // This does not mean BROKEN. The projection was there, but it gone now. + // Consistency mechanism might have kicked in and fixed the shadow. + // What we really want here is a thombstone projection or a refreshed projection. + result.muteLastSubresultError(); + refreshContextAfterShadowNotFound(context, projContext, options, task, result); } catch (CommunicationException | SchemaException | ConfigurationException | SecurityViolationException | RuntimeException | Error e) { @@ -1104,14 +1109,14 @@ private void finishLoadOfProjectionContext(LensContext } } } - + } projContext.setFresh(true); } } else { projectionObject = projContext.getObjectCurrent(); if (projectionObjectOid != null) { - projContext.setExists(true); + projContext.setExists(ShadowUtil.isExists(projectionObject.asObjectable())); } } @@ -1250,6 +1255,9 @@ public void loadFullShadow(LensContext context, LensPr GetOperationOptions getOptions = GetOperationOptions.createAllowNotFound(); getOptions.setPointInTimeType(PointInTimeType.FUTURE); + if (projCtx.isDoReconciliation()) { + getOptions.setForceRefresh(true); + } if (SchemaConstants.CHANGE_CHANNEL_DISCOVERY_URI.equals(context.getChannel())) { LOGGER.trace("Loading full resource object {} from provisioning - with doNotDiscover to avoid loops; reason: {}", projCtx, reason); // Avoid discovery loops @@ -1257,9 +1265,9 @@ public void loadFullShadow(LensContext context, LensPr } else { LOGGER.trace("Loading full resource object {} from provisioning (discovery enabled), reason: {}, channel: {}", projCtx, reason, context.getChannel()); } + Collection> options = SelectorOptions.createCollection(getOptions); + applyAttributesToGet(projCtx, options); try { - Collection> options = SelectorOptions.createCollection(getOptions); - applyAttributesToGet(projCtx, options); PrismObject objectCurrent = provisioningService.getObject(ShadowType.class, projCtx.getOid(), options, task, result); Validate.notNull(objectCurrent.getOid()); @@ -1274,76 +1282,81 @@ public void loadFullShadow(LensContext context, LensPr } catch (ObjectNotFoundException ex) { LOGGER.debug("Load of full resource object {} ended with ObjectNotFoundException (options={})", projCtx, getOptions); - if (projCtx.isDelete()){ - //this is OK, shadow was deleted, but we will continue in processing with old shadow..and set it as full so prevent from other full loading - projCtx.setFullShadow(true); - } else { + result.muteLastSubresultError(); + refreshContextAfterShadowNotFound(context, projCtx, options, task, result); + } - boolean compensated = false; - if (!GetOperationOptions.isDoNotDiscovery(getOptions)) { - // The account might have been re-created by the discovery. - // Reload focus, try to find out if there is a new matching link (and the old is gone) - LensFocusContext focusContext = context.getFocusContext(); - if (focusContext != null) { - Class focusClass = focusContext.getObjectTypeClass(); - if (FocusType.class.isAssignableFrom(focusClass)) { - LOGGER.trace("Reloading focus to check for new links"); - PrismObject focusCurrent = cacheRepositoryService.getObject(focusContext.getObjectTypeClass(), focusContext.getOid(), null, result); - FocusType focusType = (FocusType) focusCurrent.asObjectable(); - for (ObjectReferenceType linkRef: focusType.getLinkRef()) { - if (linkRef.getOid().equals(projCtx.getOid())) { - // The deleted shadow is still in the linkRef. This should not happen, but it obviously happens sometimes. - // Maybe some strange race condition? Anyway, we want a robust behavior and this linkeRef should NOT be there. - // So simple remove it. - LOGGER.warn("The OID "+projCtx.getOid()+" of deleted shadow still exists in the linkRef after discovery ("+focusCurrent+"), removing it"); - ReferenceDelta unlinkDelta = ReferenceDelta.createModificationDelete( - FocusType.F_LINK_REF, focusContext.getObjectDefinition(), linkRef.asReferenceValue().clone()); - focusContext.swallowToSecondaryDelta(unlinkDelta); - continue; - } - boolean found = false; - for (LensProjectionContext pCtx: context.getProjectionContexts()) { - if (linkRef.getOid().equals(pCtx.getOid())) { - found = true; - break; - } - } - if (!found) { - // This link is new, it is not in the existing lens context - PrismObject newLinkRepoShadow = cacheRepositoryService.getObject(ShadowType.class, linkRef.getOid(), null, result); - if (ShadowUtil.matches(newLinkRepoShadow, projCtx.getResourceShadowDiscriminator())) { - LOGGER.trace("Found new matching link: {}, updating projection context", newLinkRepoShadow); - LOGGER.trace("Applying definition from provisioning first."); // MID-3317 - provisioningService.applyDefinition(newLinkRepoShadow, task, result); - projCtx.setObjectCurrent(newLinkRepoShadow); - projCtx.setOid(newLinkRepoShadow.getOid()); - projCtx.recompute(); - compensated = true; - break; - } else { - LOGGER.trace("Found new link: {}, but skipping it because it does not match the projection context", newLinkRepoShadow); - } - } + projCtx.recompute(); + + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Loaded full resource object:\n{}", projCtx.debugDump(1)); + } + } + + public void refreshContextAfterShadowNotFound(LensContext context, LensProjectionContext projCtx, Collection> options, Task task, OperationResult result) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + if (projCtx.isDelete()){ + //this is OK, shadow was deleted, but we will continue in processing with old shadow..and set it as full so prevent from other full loading + projCtx.setFullShadow(true); + return; + } + + boolean compensated = false; + if (!GetOperationOptions.isDoNotDiscovery(SelectorOptions.findRootOptions(options))) { + // The account might have been re-created by the discovery. + // Reload focus, try to find out if there is a new matching link (and the old is gone) + LensFocusContext focusContext = context.getFocusContext(); + if (focusContext != null) { + Class focusClass = focusContext.getObjectTypeClass(); + if (FocusType.class.isAssignableFrom(focusClass)) { + LOGGER.trace("Reloading focus to check for new links"); + PrismObject focusCurrent = cacheRepositoryService.getObject(focusContext.getObjectTypeClass(), focusContext.getOid(), null, result); + FocusType focusType = (FocusType) focusCurrent.asObjectable(); + for (ObjectReferenceType linkRef: focusType.getLinkRef()) { + if (linkRef.getOid().equals(projCtx.getOid())) { + // The deleted shadow is still in the linkRef. This should not happen, but it obviously happens sometimes. + // Maybe some strange race condition? Anyway, we want a robust behavior and this linkeRef should NOT be there. + // So simple remove it. + LOGGER.warn("The OID "+projCtx.getOid()+" of deleted shadow still exists in the linkRef after discovery ("+focusCurrent+"), removing it"); + ReferenceDelta unlinkDelta = ReferenceDelta.createModificationDelete( + FocusType.F_LINK_REF, focusContext.getObjectDefinition(), linkRef.asReferenceValue().clone()); + focusContext.swallowToSecondaryDelta(unlinkDelta); + continue; + } + boolean found = false; + for (LensProjectionContext pCtx: context.getProjectionContexts()) { + if (linkRef.getOid().equals(pCtx.getOid())) { + found = true; + break; + } + } + if (!found) { + // This link is new, it is not in the existing lens context + PrismObject newLinkRepoShadow = cacheRepositoryService.getObject(ShadowType.class, linkRef.getOid(), null, result); + if (ShadowUtil.matches(newLinkRepoShadow, projCtx.getResourceShadowDiscriminator())) { + LOGGER.trace("Found new matching link: {}, updating projection context", newLinkRepoShadow); + LOGGER.trace("Applying definition from provisioning first."); // MID-3317 + provisioningService.applyDefinition(newLinkRepoShadow, task, result); + projCtx.setObjectCurrent(newLinkRepoShadow); + projCtx.setOid(newLinkRepoShadow.getOid()); + projCtx.recompute(); + compensated = true; + break; + } else { + LOGGER.trace("Found new link: {}, but skipping it because it does not match the projection context", newLinkRepoShadow); } } } - - } - - if (!compensated) { - LOGGER.trace("ObjectNotFound error is not compensated, setting context to thombstone"); - projCtx.getResourceShadowDiscriminator().setThombstone(true); - projCtx.setExists(false); - projCtx.setFullShadow(false); } } - } - - projCtx.recompute(); + } - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Loaded full resource object:\n{}", projCtx.debugDump(1)); + if (!compensated) { + LOGGER.trace("ObjectNotFound error is not compensated, setting context to thombstone"); + projCtx.getResourceShadowDiscriminator().setThombstone(true); + projCtx.setExists(false); + projCtx.setFullShadow(false); } } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/DependencyProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/DependencyProcessor.java index 78e9f93d05c..94861a0a655 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/DependencyProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/DependencyProcessor.java @@ -21,6 +21,9 @@ import java.util.List; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; +import com.evolveum.prism.xml.ns._public.types_3.ChangeTypeType; +import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -562,42 +565,54 @@ public void checkDependenciesFinal(LensContext context } } - private boolean wasProvisioned(LensProjectionContext accountContext, int executionWave) { - int accountWave = accountContext.getWave(); + private boolean wasProvisioned(LensProjectionContext projectionContext, int executionWave) { + int accountWave = projectionContext.getWave(); if (accountWave >= executionWave) { // This had no chance to be provisioned yet, so we assume it will be provisioned return true; } - if (accountContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN - || accountContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.IGNORE) { + if (projectionContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN + || projectionContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.IGNORE) { return false; } - - - PrismObject objectCurrent = accountContext.getObjectCurrent(); - if (objectCurrent != null && objectCurrent.asObjectable().getFailedOperationType() != null) { - // There is unfinished operation in the shadow. We cannot continue. + + PrismObject objectCurrent = projectionContext.getObjectCurrent(); + if (objectCurrent == null) { return false; } - if (accountContext.isExists()) { - return true; + + if (!projectionContext.isExists()) { + return false; } + + // This is getting tricky now. We cannot simply check for projectionContext.isExists() here. + // entire projection is loaded with pointInTime=future. Therefore this does NOT + // reflect actual situation. If there is a pending operation to create the object then + // isExists will in fact be true even if the projection was not provisioned yet. + // We need to check pending operations to see if there is pending add delta. + if (hasPendingAddOperation(objectCurrent)) { + return false; + } + + return true; + } - if (accountContext.isAdd()) { - List> executedDeltas = accountContext.getExecutedDeltas(); - if (executedDeltas == null || executedDeltas.isEmpty()) { - return false; + private boolean hasPendingAddOperation(PrismObject objectCurrent) { + List pendingOperations = objectCurrent.asObjectable().getPendingOperation(); + for (PendingOperationType pendingOperation: pendingOperations) { + if (pendingOperation.getExecutionStatus() != PendingOperationExecutionStatusType.EXECUTING) { + continue; } - for (LensObjectDeltaOperation executedDelta: executedDeltas) { - OperationResult executionResult = executedDelta.getExecutionResult(); - if (executionResult == null || !executionResult.isSuccess()) { - return false; - } + ObjectDeltaType delta = pendingOperation.getDelta(); + if (delta == null) { + continue; + } + if (delta.getChangeType() != ChangeTypeType.ADD) { + continue; } return true; } - return false; } diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentProcessor.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentProcessor.java index 8447bcc92b2..10f37407724 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentProcessor.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/lens/projector/focus/AssignmentProcessor.java @@ -116,6 +116,7 @@ public class AssignmentProcessor { private RepositoryService repositoryService; @Autowired + @Qualifier("modelObjectResolver") private ObjectResolver objectResolver; @Autowired diff --git a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/UserProfileServiceImpl.java b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/UserProfileServiceImpl.java index a78fa50a405..ac93007c5a6 100644 --- a/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/UserProfileServiceImpl.java +++ b/model/model-impl/src/main/java/com/evolveum/midpoint/model/impl/security/UserProfileServiceImpl.java @@ -106,7 +106,7 @@ public class UserProfileServiceImpl implements UserProfileService, UserDetailsSe @Qualifier("cacheRepositoryService") private RepositoryService repositoryService; - @Autowired private ObjectResolver objectResolver; + @Autowired @Qualifier("modelObjectResolver") private ObjectResolver objectResolver; @Autowired private SystemObjectCache systemObjectCache; @Autowired private MappingFactory mappingFactory; @Autowired private MappingEvaluator mappingEvaluator; diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAbstractAssignmentEvaluator.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAbstractAssignmentEvaluator.java index bbfc65269b0..e992f95b24b 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAbstractAssignmentEvaluator.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAbstractAssignmentEvaluator.java @@ -38,6 +38,7 @@ import com.evolveum.midpoint.prism.delta.DeltaSetTriple; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.ContextConfiguration; @@ -89,6 +90,7 @@ public abstract class TestAbstractAssignmentEvaluator extends AbstractLensTest { private RepositoryService repositoryService; @Autowired + @Qualifier("modelObjectResolver") private ObjectResolver objectResolver; @Autowired diff --git a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor2.java b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor2.java index bd80a611e4e..3b921b33417 100644 --- a/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor2.java +++ b/model/model-impl/src/test/java/com/evolveum/midpoint/model/impl/lens/TestAssignmentProcessor2.java @@ -52,6 +52,7 @@ import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.testng.annotations.Test; import javax.xml.namespace.QName; @@ -116,7 +117,7 @@ public class TestAssignmentProcessor2 extends AbstractLensTest { @Autowired private AssignmentProcessor assignmentProcessor; @Autowired private Clock clock; - @Autowired private ObjectResolver objectResolver; + @Autowired @Qualifier("modelObjectResolver") private ObjectResolver objectResolver; @Autowired private MappingFactory mappingFactory; @Autowired private MappingEvaluator mappingEvaluator; @Autowired private ActivationComputer activationComputer; diff --git a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestMultiResource.java b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestMultiResource.java index 2a4c24a061d..19e3e919a6e 100644 --- a/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestMultiResource.java +++ b/model/model-intest/src/test/java/com/evolveum/midpoint/model/intest/TestMultiResource.java @@ -90,7 +90,7 @@ public class TestMultiResource extends AbstractInitializedModelIntegrationTest { protected static final String RESOURCE_DUMMY_IVORY_NAME = "ivory"; protected static final String RESOURCE_DUMMY_IVORY_NAMESPACE = MidPointConstants.NS_RI; - // IVORY dummy resource has a RELAXED dependency on default dummy resource + // BEIGE dummy resource has a RELAXED dependency on default dummy resource protected static final File RESOURCE_DUMMY_BEIGE_FILE = new File(TEST_DIR, "resource-dummy-beige.xml"); protected static final String RESOURCE_DUMMY_BEIGE_OID = "10000000-0000-0000-0000-00000001b504"; protected static final String RESOURCE_DUMMY_BEIGE_NAME = "beige"; @@ -120,7 +120,7 @@ public class TestMultiResource extends AbstractInitializedModelIntegrationTest { protected static final String RESOURCE_DUMMY_DAVID_NAMESPACE = MidPointConstants.NS_RI; protected static final File RESOURCE_DUMMY_GOLIATH_FILE = new File(TEST_DIR, "resource-dummy-goliath.xml"); - protected static final String RESOURCE_DUMMY_GOLIATH_OID = "10000000-0000-0000-0000-000000300001"; + protected static final String RESOURCE_DUMMY_GOLIATH_OID = "10000000-0000-0000-0000-000000300002"; protected static final String RESOURCE_DUMMY_GOLIATH_NAME = "goliath"; protected static final String RESOURCE_DUMMY_GOLIATH_NAMESPACE = MidPointConstants.NS_RI; @@ -571,6 +571,7 @@ public void test224JackKillBeigeAccounAndRecompute() throws Exception { // THEN displayThen(TEST_NAME); result.computeStatus(); + display("Result", result); TestUtil.assertSuccess(result); PrismObject userJack = getUser(USER_JACK_OID); @@ -942,12 +943,7 @@ public void jackAssignRoleDummiesError(final String TEST_NAME, String roleOid, S // THEN result.computeStatus(); display(result); - if (expectAccount) { - TestUtil.assertResultStatus(result, OperationResultStatus.PARTIAL_ERROR); - } else { - TestUtil.assertStatus(result, OperationResultStatus.PARTIAL_ERROR); -// TestUtil.assertPartialError(result); - } + TestUtil.assertResultStatus(result, OperationResultStatus.IN_PROGRESS); PrismObject userJack = getUser(USER_JACK_OID); assertAssignedRole(USER_JACK_OID, roleOid, task, result); @@ -987,7 +983,7 @@ public void jackUnAssignRoleDummiesError(final String TEST_NAME, String roleOid) // there is a failure while reading dummy account - it was not created // because of unavailability of the resource..but it is OK.. OperationResultStatus status = result.getStatus(); - if (status != OperationResultStatus.SUCCESS && status != OperationResultStatus.PARTIAL_ERROR) { + if (status != OperationResultStatus.HANDLED_ERROR && status != OperationResultStatus.PARTIAL_ERROR) { AssertJUnit.fail("Expected result success or partial error status, but was "+status); } @@ -1961,10 +1957,10 @@ public void test419DavidAndGoliathUnassignRole() throws Exception { result.computeStatus(); TestUtil.assertSuccess(result, 2); - PrismObject userAfter = getUser(userBefore.getOid()); - display("User after fight", userAfter); - assertUser(userAfter, userBefore.getOid(), USER_FIELD_NAME, USER_WORLD_FULL_NAME, null, null); - assertLinks(userAfter, 0); + assertUserAfter(userBefore.getOid()) + .assertName(USER_FIELD_NAME) + .assertFullName(USER_WORLD_FULL_NAME) + .assertLinks(0); assertNoDummyAccount(RESOURCE_DUMMY_DAVID_NAME, USER_FIELD_NAME); assertNoDummyAccount(RESOURCE_DUMMY_GOLIATH_NAME, USER_FIELD_NAME); @@ -2000,14 +1996,16 @@ public void test420DavidAndGoliathAssignRoleGoliathDown() throws Exception { UserType.F_LOCALITY, prismContext); userDelta.addModificationReplaceProperty(UserType.F_TITLE); executeChanges(userDelta, null, task, result); - result.computeStatus(); - TestUtil.assertSuccess(result); + assertSuccess(result); - userBefore = findUserByUsername(USER_FIELD_NAME); - display("User before", userBefore); + userBefore = assertUserBeforeByUsername(USER_FIELD_NAME) + .assertLinks(0) + .getObject(); + + assertNoDummyAccount(RESOURCE_DUMMY_DAVID_NAME, USER_FIELD_NAME); + assertNoDummyAccount(RESOURCE_DUMMY_GOLIATH_NAME, USER_FIELD_NAME); getDummyResource(RESOURCE_DUMMY_GOLIATH_NAME).setBreakMode(BreakMode.NETWORK); - dummyAuditService.clear(); // WHEN @@ -2016,25 +2014,29 @@ public void test420DavidAndGoliathAssignRoleGoliathDown() throws Exception { // THEN displayThen(TEST_NAME); - result.computeStatus(); - // Inner errors are expected - TestUtil.assertPartialError(result); - - PrismObject userAfter = getUser(userBefore.getOid()); - display("User after fight", userAfter); - assertUser(userAfter, userBefore.getOid(), USER_FIELD_NAME, USER_WORLD_FULL_NAME, null, null); - assertAccount(userAfter, RESOURCE_DUMMY_DAVID_OID); - assertAccount(userAfter, RESOURCE_DUMMY_GOLIATH_OID); // This is unfinished shadow - assertLinks(userAfter, 2); - - assertDummyAccount(RESOURCE_DUMMY_DAVID_NAME, USER_FIELD_NAME, USER_WORLD_FULL_NAME, true); - - assertDummyAccountAttribute(RESOURCE_DUMMY_DAVID_NAME, USER_FIELD_NAME, - DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_WEAPON_NAME, "rock (field) take"); - - assertUserProperty(userAfter, UserType.F_LOCALITY, PrismTestUtil.createPolyString("rock (field) take throw")); + result.computeStatus(); // explicitly recompute status here. It was computed before. + // Inner errors are expected - but those should be pending on retry + assertInProgress(result); + + assertUserAfter(userBefore.getOid()) + .assertName(USER_FIELD_NAME) + .assertFullName(USER_WORLD_FULL_NAME) + .assertLocality("rock (field) take throw") + .assertLinks(2) + .assertHasProjectionOnResource(RESOURCE_DUMMY_DAVID_OID) + .projectionOnResource(RESOURCE_DUMMY_GOLIATH_OID) // This is unfinished shadow + .display() + .hasUnfinishedPendingOperations(); + + getDummyResource(RESOURCE_DUMMY_GOLIATH_NAME).resetBreakMode(); + + assertDummyAccountByUsername(RESOURCE_DUMMY_DAVID_NAME, USER_FIELD_NAME) + .assertFullName(USER_WORLD_FULL_NAME) + .assertEnabled() + .assertAttribute(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_WEAPON_NAME, "rock (field) take"); // Goliath is down. No account. + assertNoDummyAccount(RESOURCE_DUMMY_GOLIATH_NAME, USER_FIELD_NAME); // Check audit display("Audit", dummyAuditService); @@ -2051,22 +2053,83 @@ public void test420DavidAndGoliathAssignRoleGoliathDown() throws Exception { dummyAuditService.assertHasDelta(2,ChangeType.MODIFY, UserType.class); } - // MID-1566 - @Test(enabled=false) + /** + * Recompute. Before retry interval. + * Even though resource is now up nothing should happen (yet). + * MID-1566 + */ + @Test + public void test421DavidAndGoliathAssignRoleGoliathUpRecompute() throws Exception { + final String TEST_NAME = "test421DavidAndGoliathAssignRoleGoliathUpRecompute"; + displayTestTitle(TEST_NAME); + + // GIVEN + assumeAssignmentPolicy(AssignmentPolicyEnforcementType.RELATIVE); + getDummyResource(RESOURCE_DUMMY_GOLIATH_NAME).resetBreakMode(); + dummyAuditService.clear(); + + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + PrismObject userBefore = findUserByUsername(USER_FIELD_NAME); + + // WHEN + recomputeUser(userBefore.getOid(), task, result); + + // THEN + displayThen(TEST_NAME); + assertSuccess(result); + + assertUserAfter(userBefore.getOid()) + .assertName(USER_FIELD_NAME) + .assertFullName(USER_WORLD_FULL_NAME) + .assertLocality("rock (field) take throw") + .assertLinks(2) + .assertHasProjectionOnResource(RESOURCE_DUMMY_DAVID_OID) + .projectionOnResource(RESOURCE_DUMMY_GOLIATH_OID) // This is unfinished shadow + .display() + .hasUnfinishedPendingOperations(); + + getDummyResource(RESOURCE_DUMMY_GOLIATH_NAME).resetBreakMode(); + + assertDummyAccountByUsername(RESOURCE_DUMMY_DAVID_NAME, USER_FIELD_NAME) + .assertFullName(USER_WORLD_FULL_NAME) + .assertEnabled() + .assertAttribute(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_WEAPON_NAME, "rock (field) take"); + + // Goliath is up. But haven't retried yet. hence no account. + assertNoDummyAccount(RESOURCE_DUMMY_GOLIATH_NAME, USER_FIELD_NAME); + + // Check audit + display("Audit", dummyAuditService); + dummyAuditService.assertRecords(2); + dummyAuditService.assertSimpleRecordSanity(); + } + + + /** + * Wait for retry interval. Then recompute. + * Even though resource is now up nothing should happen (yet) as recompute + * is meant to be "lightweight" and it should not trigger refresh. + * MID-1566 + */ + @Test public void test422DavidAndGoliathAssignRoleGoliathUpRecompute() throws Exception { final String TEST_NAME = "test422DavidAndGoliathAssignRoleGoliathUpRecompute"; displayTestTitle(TEST_NAME); // GIVEN assumeAssignmentPolicy(AssignmentPolicyEnforcementType.RELATIVE); + getDummyResource(RESOURCE_DUMMY_GOLIATH_NAME).resetBreakMode(); + dummyAuditService.clear(); + + clockForward("PT1H"); Task task = createTask(TEST_NAME); OperationResult result = task.getResult(); PrismObject userBefore = findUserByUsername(USER_FIELD_NAME); - getDummyResource(RESOURCE_DUMMY_GOLIATH_NAME).setBreakMode(BreakMode.NONE); - // WHEN recomputeUser(userBefore.getOid(), task, result); @@ -2074,24 +2137,64 @@ public void test422DavidAndGoliathAssignRoleGoliathUpRecompute() throws Exceptio displayThen(TEST_NAME); assertSuccess(result); + assertUserAfter(userBefore.getOid()) + .assertName(USER_FIELD_NAME) + .assertFullName(USER_WORLD_FULL_NAME) + .assertLocality("rock (field) take throw") + .assertLinks(2) + .assertHasProjectionOnResource(RESOURCE_DUMMY_DAVID_OID) + .projectionOnResource(RESOURCE_DUMMY_GOLIATH_OID) // This is unfinished shadow + .display() + .hasUnfinishedPendingOperations(); + + getDummyResource(RESOURCE_DUMMY_GOLIATH_NAME).resetBreakMode(); + + assertDummyAccountByUsername(RESOURCE_DUMMY_DAVID_NAME, USER_FIELD_NAME) + .assertFullName(USER_WORLD_FULL_NAME) + .assertEnabled() + .assertAttribute(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_WEAPON_NAME, "rock (field) take"); + + // Goliath is up. But haven't retried yet. hence no account. + assertNoDummyAccount(RESOURCE_DUMMY_GOLIATH_NAME, USER_FIELD_NAME); + + // Check audit + display("Audit", dummyAuditService); + dummyAuditService.assertRecords(0); + } + + /** + * Reconcile after retry interval. Now we are rocking ... and things will finally + * get fixed. + * MID-1566 + */ + @Test + public void test423DavidAndGoliathAssignRoleGoliathUpReconcile() throws Exception { + final String TEST_NAME = "test423DavidAndGoliathAssignRoleGoliathUpReconcile"; + displayTestTitle(TEST_NAME); + + // GIVEN + assumeAssignmentPolicy(AssignmentPolicyEnforcementType.RELATIVE); + getDummyResource(RESOURCE_DUMMY_GOLIATH_NAME).resetBreakMode(); + dummyAuditService.clear(); + + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + PrismObject userBefore = findUserByUsername(USER_FIELD_NAME); + + // WHEN + reconcileUser(userBefore.getOid(), task, result); + + // THEN + displayThen(TEST_NAME); + assertSuccess(result); + assertDavidGoliath(userBefore.getOid(), "rock", USER_FIELD_NAME, true, true, true); // Check audit display("Audit", dummyAuditService); - dummyAuditService.assertRecords(4); + dummyAuditService.assertRecords(2); dummyAuditService.assertSimpleRecordSanity(); - dummyAuditService.assertAnyRequestDeltas(); - dummyAuditService.assertExecutionDeltas(0,2); - dummyAuditService.assertHasDelta(0,ChangeType.MODIFY, UserType.class); - dummyAuditService.assertHasDelta(0,ChangeType.MODIFY, ShadowType.class); - dummyAuditService.assertExecutionDeltas(1,2); - dummyAuditService.assertHasDelta(1,ChangeType.MODIFY, UserType.class); - dummyAuditService.assertHasDelta(1,ChangeType.MODIFY, ShadowType.class); - dummyAuditService.assertExecutionDeltas(2,2); - dummyAuditService.assertHasDelta(2,ChangeType.MODIFY, UserType.class); - dummyAuditService.assertHasDelta(2,ChangeType.MODIFY, ShadowType.class); - dummyAuditService.assertExecutionSuccess(); - } @Test @@ -2101,12 +2204,13 @@ public void test429DavidAndGoliathUnassignRole() throws Exception { // GIVEN assumeAssignmentPolicy(AssignmentPolicyEnforcementType.RELATIVE); + getDummyResource(RESOURCE_DUMMY_GOLIATH_NAME).resetBreakMode(); + dummyAuditService.clear(); Task task = createTask(TEST_NAME); OperationResult result = task.getResult(); PrismObject userBefore = findUserByUsername(USER_FIELD_NAME); - dummyAuditService.clear(); // WHEN displayWhen(TEST_NAME); @@ -2114,15 +2218,15 @@ public void test429DavidAndGoliathUnassignRole() throws Exception { // THEN displayThen(TEST_NAME); - result.computeStatus(); -// TestUtil.assertSuccess(result, 2); + assertSuccess(result); - getDummyResource(RESOURCE_DUMMY_GOLIATH_NAME).setBreakMode(BreakMode.NONE); - - PrismObject userAfter = getUser(userBefore.getOid()); - display("User after fight", userAfter); - assertUser(userAfter, userBefore.getOid(), USER_FIELD_NAME, USER_WORLD_FULL_NAME, null, null); - assertLinks(userAfter, 0); + assertUserAfter(userBefore.getOid()) + .assertName(USER_FIELD_NAME) + .assertFullName(USER_WORLD_FULL_NAME) + .singleLink() + .resolveTarget() + .display() + .assertDead(); assertNoDummyAccount(RESOURCE_DUMMY_DAVID_NAME, USER_FIELD_NAME); assertNoDummyAccount(RESOURCE_DUMMY_GOLIATH_NAME, USER_FIELD_NAME); @@ -2136,10 +2240,9 @@ public void test429DavidAndGoliathUnassignRole() throws Exception { dummyAuditService.assertExecutionDeltas(1,2); dummyAuditService.assertHasDelta(0,ChangeType.MODIFY, UserType.class); dummyAuditService.assertHasDelta(1,ChangeType.DELETE, ShadowType.class); - dummyAuditService.assertExecutionDeltas(2,2); - dummyAuditService.assertHasDelta(1,ChangeType.MODIFY, UserType.class); + dummyAuditService.assertExecutionDeltas(2,1); dummyAuditService.assertHasDelta(1,ChangeType.DELETE, ShadowType.class); - dummyAuditService.assertExecutionOutcome(OperationResultStatus.PARTIAL_ERROR); + dummyAuditService.assertExecutionSuccess(); } @Test @@ -2174,9 +2277,7 @@ public void test430DavidAndGoliathAssignRoleDavidDown() throws Exception { // THEN displayThen(TEST_NAME); - result.computeStatus(); - // Inner errors are expected - TestUtil.assertPartialError(result); + assertInProgress(result); PrismObject userAfter = getUser(userBefore.getOid()); display("User after fight", userAfter); diff --git a/model/model-intest/src/test/resources/logback-test.xml b/model/model-intest/src/test/resources/logback-test.xml index 86bbdb310a9..d1470529712 100644 --- a/model/model-intest/src/test/resources/logback-test.xml +++ b/model/model-intest/src/test/resources/logback-test.xml @@ -47,7 +47,7 @@ - + @@ -55,15 +55,15 @@ - + - - + + @@ -72,8 +72,8 @@ - - + + @@ -82,10 +82,10 @@ - - + + - + diff --git a/model/model-intest/src/test/resources/multi/role-dummies-beige.xml b/model/model-intest/src/test/resources/multi/role-dummies-beige.xml index dbba61e4fdf..70ba436df19 100644 --- a/model/model-intest/src/test/resources/multi/role-dummies-beige.xml +++ b/model/model-intest/src/test/resources/multi/role-dummies-beige.xml @@ -1,5 +1,5 @@ account ri:title @@ -43,7 +43,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 392fef68ee8..0da382185ac 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 @@ -160,6 +160,9 @@ import com.evolveum.midpoint.test.DummyAuditService; import com.evolveum.midpoint.test.DummyResourceContoller; import com.evolveum.midpoint.test.IntegrationTestTools; +import com.evolveum.midpoint.test.asserter.DummyAccountAsserter; +import com.evolveum.midpoint.test.asserter.FocusAsserter; +import com.evolveum.midpoint.test.asserter.UserAsserter; import com.evolveum.midpoint.test.util.MidPointAsserts; import com.evolveum.midpoint.test.util.TestUtil; import com.evolveum.midpoint.util.exception.CommonException; @@ -363,6 +366,10 @@ protected DummyResourceContoller getDummyResourceController(String name) { protected DummyResourceContoller getDummyResourceController() { return getDummyResourceController(null); } + + protected DummyAccountAsserter assertDummyAccountByUsername(String dummyResourceName, String username) throws ConnectException, FileNotFoundException, SchemaViolationException, ConflictException { + return getDummyResourceController(dummyResourceName).assertAccountByUsername(username); + } protected DummyResource getDummyResource(String name) { return dummyResourceCollection.getDummyResource(name); @@ -5230,4 +5237,39 @@ protected PendingOperationType findPendingOperation(PrismObject shad return null; } + protected UserAsserter assertUserAfter(String oid) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + UserAsserter asserter = assertUser(oid, "after"); + asserter.display(); + asserter.assertOid(oid); + return asserter; + } + + protected UserAsserter assertUserBefore(String oid) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + UserAsserter asserter = assertUser(oid, "before"); + asserter.display(); + asserter.assertOid(oid); + return asserter; + } + + protected UserAsserter assertUserBeforeByUsername(String username) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + UserAsserter asserter = assertUserByUsername(username, "before"); + asserter.display(); + asserter.assertName(username); + return asserter; + } + + protected UserAsserter assertUser(String oid, String message) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + PrismObject user = getUser(oid); + UserAsserter asserter = UserAsserter.forUser(user, message); + asserter.setObjectResolver(repoObjectResolver); + return asserter; + } + + protected UserAsserter assertUserByUsername(String username, String message) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + PrismObject user = findUserByUsername(username); + UserAsserter asserter = UserAsserter.forUser(user, message); + asserter.setObjectResolver(repoObjectResolver); + return asserter; + } + } diff --git a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportCreateTaskHandler.java b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportCreateTaskHandler.java index f1b8aa72113..13a2fc3a6bc 100644 --- a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportCreateTaskHandler.java +++ b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportCreateTaskHandler.java @@ -57,6 +57,7 @@ import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import com.evolveum.midpoint.model.api.ModelService; @@ -128,7 +129,7 @@ public class ReportCreateTaskHandler implements TaskHandler { @Autowired private ModelService modelService; @Autowired private PrismContext prismContext; @Autowired private ReportService reportService; - @Autowired private ObjectResolver objectResolver; + @Autowired @Qualifier("modelObjectResolver") private ObjectResolver objectResolver; @Autowired private CommandLineScriptExecutor commandLineScriptExecutor; @PostConstruct diff --git a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportServiceImpl.java b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportServiceImpl.java index c101dc4fcbb..ffff9693604 100644 --- a/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportServiceImpl.java +++ b/model/report-impl/src/main/java/com/evolveum/midpoint/report/impl/ReportServiceImpl.java @@ -30,6 +30,7 @@ import com.evolveum.midpoint.prism.PrismContainerValue; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import com.evolveum.midpoint.audit.api.AuditEventRecord; @@ -78,7 +79,7 @@ public class ReportServiceImpl implements ReportService { @Autowired private TaskManager taskManager; @Autowired private PrismContext prismContext; @Autowired private ExpressionFactory expressionFactory; - @Autowired private ObjectResolver objectResolver; + @Autowired @Qualifier("modelObjectResolver") private ObjectResolver objectResolver; @Autowired private AuditService auditService; @Autowired private FunctionLibrary logFunctionLibrary; @Autowired private FunctionLibrary basicFunctionLibrary; diff --git a/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ProvisioningService.java b/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ProvisioningService.java index e762eec3799..05b6af79f76 100644 --- a/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ProvisioningService.java +++ b/provisioning/provisioning-api/src/main/java/com/evolveum/midpoint/provisioning/api/ProvisioningService.java @@ -312,6 +312,10 @@ String modifyObject(Class type, String oid, Collection /** *

Deletes object with specified OID.

*

+ * Delete operation always deletes the resource object - or at least tries to. But this operation may + * or may not delete the repository shadow. The shadow may remain in a dead (thombstone) state. + * In that case the delete operation returns such shadow to indicate that repository shadow was not deleted. + *

* Must fail if object with specified OID * does not exists. Should be atomic. *

@@ -322,6 +326,8 @@ String modifyObject(Class type, String oid, Collection * scripts that should be executed before of after operation * @param parentResult * parent OperationResult (in/out) + * + * @return Dead repository shadow - if it exists after delete. Otherwise returns null. * * @throws ObjectNotFoundException * specified object does not exist @@ -333,7 +339,7 @@ String modifyObject(Class type, String oid, Collection * @throws GenericConnectorException * unknown connector framework error */ - void deleteObject(Class type, String oid, ProvisioningOperationOptions option, OperationProvisioningScriptsType scripts, Task task, OperationResult parentResult) + PrismObject deleteObject(Class type, String oid, ProvisioningOperationOptions option, OperationProvisioningScriptsType scripts, Task task, OperationResult parentResult) throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException; /** diff --git a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ProvisioningServiceImpl.java b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ProvisioningServiceImpl.java index 8685d725e4c..d6aae2e10d5 100644 --- a/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ProvisioningServiceImpl.java +++ b/provisioning/provisioning-impl/src/main/java/com/evolveum/midpoint/provisioning/impl/ProvisioningServiceImpl.java @@ -667,7 +667,7 @@ public String modifyObject(Class type, String oid, } @Override - public void deleteObject(Class type, String oid, ProvisioningOperationOptions options, OperationProvisioningScriptsType scripts, + public PrismObject deleteObject(Class type, String oid, ProvisioningOperationOptions options, OperationProvisioningScriptsType scripts, Task task, OperationResult parentResult) throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { @@ -687,11 +687,13 @@ public void deleteObject(Class type, String oid, Provi LOGGER.trace("**PROVISIONING: Object from repository to delete:\n{}", object.debugDump()); } + PrismObject deadShadow = null; + if (object.canRepresent(ShadowType.class) && !ProvisioningOperationOptions.isRaw(options)) { try { - shadowCache.deleteShadow((PrismObject)object, options, scripts, task, result); + deadShadow = (PrismObject) shadowCache.deleteShadow((PrismObject)object, options, scripts, task, result); } catch (CommunicationException e) { ProvisioningUtil.recordFatalError(LOGGER, result, "Couldn't delete object: communication problem: " + e.getMessage(), e); @@ -741,6 +743,8 @@ public void deleteObject(Class type, String oid, Provi result.computeStatus(); } result.cleanupResult(); + + return deadShadow; } /* (non-Javadoc) 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 5b958e4ace8..748422c2e83 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 @@ -203,7 +203,9 @@ public PrismObject getShadow(String oid, PrismObject rep throw e; } - repositoryShadow = refreshShadow(repositoryShadow, task, parentResult); + if (GetOperationOptions.isForceRefresh(rootOptions)) { + repositoryShadow = refreshShadow(repositoryShadow, task, parentResult); + } if (repositoryShadow == null) { // Dead shadow was just removed // TODO: is this OK? What about re-appeared objects @@ -1042,7 +1044,7 @@ private ProvisioningOperationState repoShadow, ProvisioningOperationOptions options, + public PrismObject deleteShadow(PrismObject repoShadow, ProvisioningOperationOptions options, OperationProvisioningScriptsType scripts, Task task, OperationResult parentResult) throws CommunicationException, GenericFrameworkException, ObjectNotFoundException, SchemaException, ConfigurationException, SecurityViolationException, ExpressionEvaluationException { @@ -1063,7 +1065,7 @@ public void deleteShadow(PrismObject repoShadow, ProvisioningOperati shadowManager.deleteShadow(ctx, repoShadow, parentResult); parentResult.recordHandledError( "Resource defined in shadow does not exists. Shadow was deleted from the repository."); - return; + return null; } else { throw ex; } @@ -1072,10 +1074,10 @@ public void deleteShadow(PrismObject repoShadow, ProvisioningOperati ProvisioningOperationState opState = new ProvisioningOperationState<>(); opState.setRepoShadow(repoShadow); - deleteShadowAttempt(ctx, options, scripts, opState, task, parentResult); + return deleteShadowAttempt(ctx, options, scripts, opState, task, parentResult); } - private void deleteShadowAttempt(ProvisioningContext ctx, + private PrismObject deleteShadowAttempt(ProvisioningContext ctx, ProvisioningOperationOptions options, OperationProvisioningScriptsType scripts, ProvisioningOperationState opState, @@ -1090,7 +1092,7 @@ private void deleteShadowAttempt(ProvisioningContext ctx, PendingOperationType duplicateOperation = shadowManager.checkAndRecordPendingDeleteOperationBeforeExecution(ctx, repoShadow, opState, task, parentResult); if (duplicateOperation != null) { parentResult.recordInProgress(); - return; + return repoShadow; } LOGGER.trace("Deleting object {} from the resource {}.", repoShadow, ctx.getResource()); @@ -1135,11 +1137,11 @@ private void deleteShadowAttempt(ProvisioningContext ctx, LOGGER.debug("DELETE {}: resource operation NOT executed, execution pending", repoShadow); } - LOGGER.trace("Deting object with oid {} form repository.", repoShadow.getOid()); XMLGregorianCalendar now = clock.currentTimeXMLGregorianCalendar(); + PrismObject resultShadow; try { - shadowManager.recordDeleteResult(ctx, repoShadow, opState, now, parentResult); + resultShadow = shadowManager.recordDeleteResult(ctx, repoShadow, opState, now, parentResult); } catch (ObjectNotFoundException ex) { parentResult.recordFatalError("Can't delete object " + repoShadow + ". Reason: " + ex.getMessage(), ex); @@ -1152,6 +1154,8 @@ private void deleteShadowAttempt(ProvisioningContext ctx, notifyAfterDelete(ctx, repoShadow, opState, task, parentResult); setParentOperationStatus(parentResult, opState, finalOperationStatus); + + return resultShadow; } private ProvisioningOperationState executeResourceDelete( 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.java index d025bcd53ac..56523c33d35 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.java @@ -1912,7 +1912,7 @@ private void compareUpdateProperty(ObjectDelta shadowDelta, } } - public void recordDeleteResult( + public PrismObject recordDeleteResult( ProvisioningContext ctx, PrismObject oldRepoShadow, ProvisioningOperationState opState, @@ -1921,9 +1921,15 @@ public void recordDeleteResult( throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException, ExpressionEvaluationException, EncryptionException { if (!opState.hasPendingOperations() && opState.isCompleted()) { - LOGGER.trace("Deleting repository {}: {}", oldRepoShadow, opState); - repositoryService.deleteObject(ShadowType.class, oldRepoShadow.getOid(), parentResult); - return; + if (oldRepoShadow.asObjectable().getPendingOperation().isEmpty()) { + LOGGER.trace("Deleting repository {}: {}", oldRepoShadow, opState); + repositoryService.deleteObject(ShadowType.class, oldRepoShadow.getOid(), parentResult); + return null; + } else { + // There are unexpired pendign operations in the shadow. We cannot delete the shadow yet. + // Therefore just mark shadow as dead. + return markShadowDead(oldRepoShadow, parentResult); + } } LOGGER.trace("Recording pending delete operation in repository {}: {}", oldRepoShadow, opState); ObjectDelta requestDelta = oldRepoShadow.createDeleteDelta(); @@ -1931,6 +1937,8 @@ public void recordDeleteResult( LOGGER.trace("Updating repository {} after DELETE operation {}, {} repository shadow modifications", oldRepoShadow, opState, internalShadowModifications.size()); modifyShadowAttributes(ctx, oldRepoShadow, internalShadowModifications, parentResult); + ObjectDelta.applyTo(oldRepoShadow, (List)internalShadowModifications); + return oldRepoShadow; } public void deleteShadow(ProvisioningContext ctx, PrismObject oldRepoShadow, OperationResult parentResult) @@ -1944,6 +1952,7 @@ public PrismObject markShadowDead(PrismObject repoShadow .item(ShadowType.F_DEAD).replace(true) .item(ShadowType.F_EXISTS).replace(false) .asItemDeltas(); + LOGGER.trace("Marking shadow {} as dead", repoShadow); try { repositoryService.modifyObject(ShadowType.class, repoShadow.getOid(), shadowChanges, parentResult); } catch (ObjectAlreadyExistsException e) { diff --git a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/dummy/TestDummyConsistency.java b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/dummy/TestDummyConsistency.java index 74270b8eab6..30a0739096d 100644 --- a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/dummy/TestDummyConsistency.java +++ b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/dummy/TestDummyConsistency.java @@ -231,9 +231,9 @@ public void test100AddAccountMorganCommunicationFailure() throws Exception { } /** - * Test add with pending operation and recovered resource. This happens when re-try task - * does not have a chance to run yet. - * Nothing significant should happen. + * Test add with pending operation and recovered resource. This happens when refresh task + * does not have a chance to run yet. The forceFresh option is NOT used here. + * Therefore nothing significant should happen. */ @Test public void test102GetAccountMorganRecovery() throws Exception { @@ -291,6 +291,46 @@ public void test104RefreshAccountMorganCommunicationFailure() throws Exception { assertSteadyResources(); } + /** + * Get account with forceRefresh while the resource is down. Retry interval is not yet reached. + * Nothing should really happen yet. + */ + @Test + public void test105GetForceRefreshAccountMorganCommunicationFailure() throws Exception { + final String TEST_NAME = "test105GetForceRefreshAccountMorganCommunicationFailure"; + displayTestTitle(TEST_NAME); + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + syncServiceMock.reset(); + + dummyResource.setBreakMode(BreakMode.NETWORK); + + Collection> options = SelectorOptions.createCollection(GetOperationOptions.createForceRefresh()); + + try { + // WHEN + displayWhen(TEST_NAME); + + provisioningService.getObject(ShadowType.class, ACCOUNT_MORGAN_OID, options, task, result); + + assertNotReached(); + + } catch (GenericConnectorException e) { + // expected + } + + // THEN + displayThen(TEST_NAME); + display("Result", result); + assertFailure(result); + syncServiceMock.assertNoNotifcations(); + + assertUncreatedMorgan(1); + + assertSteadyResources(); + } + /** * Wait for retry interval to pass. Now provisioning should retry add operation. * But no luck yet. Resource is still down. @@ -780,8 +820,8 @@ public void test130ModifyMorganFullNameAgainCommunicationFailure() throws Except Task task = createTask(TEST_NAME); OperationResult result = task.getResult(); syncServiceMock.reset(); - dummyResource.setBreakMode(BreakMode.NETWORK); + lastRequestStartTs = lastAttemptStartTs = clock.currentTimeXMLGregorianCalendar(); ObjectDelta delta = ObjectDelta.createModificationReplaceProperty(ShadowType.class, @@ -805,7 +845,123 @@ public void test130ModifyMorganFullNameAgainCommunicationFailure() throws Except } /** - * Wait for retry interval to pass. Now provisioning should retry the operation. + * Wait for retry interval to pass. Get account, but do NOT use forceRefresh option. + * Nothing should happen. + * Resource is still down. + * Get operation should return repository shadow because staleness option is null. + * Partial error should be indicated. + */ + @Test + public void test132GetAccountMorganCommunicationFailure() throws Exception { + final String TEST_NAME = "test132GetAccountMorganCommunicationFailure"; + displayTestTitle(TEST_NAME); + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + clockForward("PT17M"); + + syncServiceMock.reset(); + dummyResource.setBreakMode(BreakMode.NETWORK); + + // WHEN + displayWhen(TEST_NAME); + + provisioningService.getObject(ShadowType.class, shadowMorganOid, null, task, result); + + // THEN + displayThen(TEST_NAME); + display("Result", result); + assertPartialError(result); + syncServiceMock.assertNoNotifcations(); + + assertUnmodifiedMorgan(1, 3, ACCOUNT_MORGAN_FULLNAME_CHM); + + assertSteadyResources(); + } + + /** + * Get account, but do NOT use forceRefresh option. + * Nothing should happen. + * Resource is still down. + * Get operation should throw an error, as there is explicit staleness=0 option. + * MID-4796 + */ + @Test(enabled=false) // MID-4796 + public void test133GetAccountMorganStalenessZeroCommunicationFailure() throws Exception { + final String TEST_NAME = "test133GetAccountMorganStalenessZeroCommunicationFailure"; + displayTestTitle(TEST_NAME); + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + syncServiceMock.reset(); + dummyResource.setBreakMode(BreakMode.NETWORK); + + Collection> options = SelectorOptions.createCollection(GetOperationOptions.createStaleness(0L)); + + try { + // WHEN + displayWhen(TEST_NAME); + + provisioningService.getObject(ShadowType.class, shadowMorganOid, options, task, result); + + assertNotReached(); + + } catch (CommunicationException e) { + // expected + } + + // THEN + displayThen(TEST_NAME); + display("Result", result); + assertFailure(result); + syncServiceMock.assertNoNotifcations(); + + assertUnmodifiedMorgan(1, 3, ACCOUNT_MORGAN_FULLNAME_CHM); + + assertSteadyResources(); + } + + + /** + * Use forceRefresh option with get operation to force refresh. + * We are over retry interval, therefore provisioning should re-try the operation. + * Resource is still down. + */ + @Test + public void test134GetAccountMorganForceRefreshRetryCommunicationFailure() throws Exception { + final String TEST_NAME = "test134GetAccountMorganForceRefreshRetryCommunicationFailure"; + displayTestTitle(TEST_NAME); + // GIVEN + Task task = createTask(TEST_NAME); + OperationResult result = task.getResult(); + + syncServiceMock.reset(); + dummyResource.setBreakMode(BreakMode.NETWORK); + + lastAttemptStartTs = clock.currentTimeXMLGregorianCalendar(); + + Collection> options = SelectorOptions.createCollection(GetOperationOptions.createForceRefresh()); + + // WHEN + displayWhen(TEST_NAME); + provisioningService.getObject(ShadowType.class, shadowMorganOid, options, task, result); + + // THEN + displayThen(TEST_NAME); + display("Result", result); + assertPartialError(result); + lastAttemptEndTs = clock.currentTimeXMLGregorianCalendar(); + syncServiceMock.assertNotifyInProgressOnly(); + + assertUnmodifiedMorgan(2, 3, ACCOUNT_MORGAN_FULLNAME_CHM); + + assertSteadyResources(); + } + + /** + * Wait for yet another retry interval to pass. Now provisioning should retry the operation. * Resource is up now and the operation can proceed. */ @Test @@ -837,7 +993,7 @@ public void test136RefreshAccountMorganRetrySuccess() throws Exception { lastAttemptEndTs = clock.currentTimeXMLGregorianCalendar(); syncServiceMock.assertNotifySuccessOnly(); - assertModifiedMorgan(2, 3, ACCOUNT_MORGAN_FULLNAME_CHM); + assertModifiedMorgan(3, 3, ACCOUNT_MORGAN_FULLNAME_CHM); // Resource -> up assertResourceStatusChangeCounterIncrements(); @@ -1036,7 +1192,7 @@ public void test180DeleteMorganCommunicationFailureAgain() throws Exception { // WHEN displayWhen(TEST_NAME); - provisioningService.deleteObject(ShadowType.class, shadowMorganOid, null, null, task, result); + PrismObject returnedShadow = provisioningService.deleteObject(ShadowType.class, shadowMorganOid, null, null, task, result); // THEN displayThen(TEST_NAME); @@ -1045,7 +1201,10 @@ public void test180DeleteMorganCommunicationFailureAgain() throws Exception { syncServiceMock.assertNotifyInProgressOnly(); assertUndeletedMorgan(1, 5); - + + assertNotNull("No shadow returned from delete", returnedShadow); +// ShadowAsserter.forShadow(returnedShadow, "returned shadow"); + assertSteadyResources(); } 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 f704cb1574f..9640b98626f 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 @@ -80,11 +80,13 @@ import com.evolveum.midpoint.schema.result.OperationResultStatus; import com.evolveum.midpoint.schema.util.FocusTypeUtil; import com.evolveum.midpoint.schema.util.MiscSchemaUtil; +import com.evolveum.midpoint.schema.util.ObjectResolver; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; import com.evolveum.midpoint.schema.util.ResourceTypeUtil; import com.evolveum.midpoint.schema.util.ShadowUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.task.api.TaskManager; +import com.evolveum.midpoint.test.asserter.FocusAsserter; import com.evolveum.midpoint.test.asserter.ShadowAsserter; import com.evolveum.midpoint.test.ldap.OpenDJController; import com.evolveum.midpoint.test.util.DerbyController; @@ -179,6 +181,10 @@ public abstract class AbstractIntegrationTest extends AbstractTestNGSpringContex @Autowired protected PrismContext prismContext; @Autowired protected MatchingRuleRegistry matchingRuleRegistry; @Autowired protected LocalizationService localizationService; + + @Autowired(required = false) + @Qualifier("repoObjectResolver") + protected ObjectResolver repoObjectResolver; // Controllers for embedded OpenDJ and Derby. The abstract test will configure it, but // it will not start @@ -209,7 +215,7 @@ public void initSystemConditional() throws Exception { InternalMonitor.reset(); InternalsConfig.setPrismMonitoring(true); prismContext.setMonitor(new InternalMonitor()); - + initSystem(initTask, result); postInitSystem(initTask, result); @@ -2388,9 +2394,9 @@ protected void assertRelationDef(List relations, QName q assertEquals("Wrong relation "+qname+" label", expectedLabel, relDef.getDisplay().getLabel()); } - protected ShadowAsserter assertRepoShadow(String oid) throws ObjectNotFoundException, SchemaException { + protected ShadowAsserter assertRepoShadow(String oid) throws ObjectNotFoundException, SchemaException { PrismObject repoShadow = getShadowRepo(oid); - ShadowAsserter asserter = ShadowAsserter.forShadow(repoShadow, "repository"); + ShadowAsserter asserter = ShadowAsserter.forShadow(repoShadow, "repository"); asserter .display() .assertBasicRepoProperties(); @@ -2407,4 +2413,5 @@ protected void assertNoRepoShadow(String oid) throws SchemaException { assertFailure(result); } } + } diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/AbstractAsserter.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/AbstractAsserter.java index b7fd83c6007..cec0adec547 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/AbstractAsserter.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/AbstractAsserter.java @@ -17,6 +17,14 @@ import org.testng.AssertJUnit; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.ObjectResolver; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; + /** * @author semancik * @@ -25,6 +33,7 @@ public abstract class AbstractAsserter { private String details; private R returnAsserter; + private ObjectResolver objectResolver; public AbstractAsserter() { this(null); @@ -40,6 +49,14 @@ public AbstractAsserter(R returnAsserter, String details) { this.returnAsserter = returnAsserter; this.details = details; } + + public ObjectResolver getObjectResolver() { + return objectResolver; + } + + public void setObjectResolver(ObjectResolver objectResolver) { + this.objectResolver = objectResolver; + } protected String getDetails() { return details; @@ -60,4 +77,21 @@ protected String descWithDetails(Object o) { public R end() { return returnAsserter; } + + protected PrismObject resolveObject(Class type, String oid) throws ObjectNotFoundException, SchemaException { + if (objectResolver == null) { + throw new IllegalStateException("Cannot resolve object "+type.getSimpleName()+" "+oid+" because there is no resolver"); + } + ObjectReferenceType ref = new ObjectReferenceType(); + ref.setOid(oid); + OperationResult result = new OperationResult("AbstractAsserter.resolveObject"); + O objectType = objectResolver.resolve(ref, type, null, desc(), null, result); + return (PrismObject) objectType.asPrismObject(); + } + + abstract protected String desc(); + + protected void copySetupTo(AbstractAsserter other) { + other.setObjectResolver(getObjectResolver()); + } } diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/FocusAsserter.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/FocusAsserter.java new file mode 100644 index 00000000000..6753d4cda2f --- /dev/null +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/FocusAsserter.java @@ -0,0 +1,210 @@ +/** + * Copyright (c) 2018 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.evolveum.midpoint.test.asserter; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.assertTrue; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.xml.namespace.QName; + +import org.apache.commons.lang.StringUtils; +import org.testng.AssertJUnit; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismReference; +import com.evolveum.midpoint.prism.util.PrismAsserts; +import com.evolveum.midpoint.test.IntegrationTestTools; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationStatusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.PendingOperationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowKindType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; + +/** + * @author semancik + * + */ +public class FocusAsserter extends PrismObjectAsserter { + + private Map> projectionCache = new HashMap<>(); + + public FocusAsserter(PrismObject focus) { + super(focus); + } + + public FocusAsserter(PrismObject focus, String details) { + super(focus, details); + } + + public FocusAsserter(PrismObject focus, R returnAsserter, String details) { + super(focus, returnAsserter, details); + } + + public static FocusAsserter forFocus(PrismObject focus) { + return new FocusAsserter<>(focus); + } + + public static FocusAsserter forFocus(PrismObject focus, String details) { + return new FocusAsserter<>(focus, details); + } + + @Override + public FocusAsserter assertOid() { + super.assertOid(); + return this; + } + + @Override + public FocusAsserter assertOid(String expected) { + super.assertOid(expected); + return this; + } + + @Override + public FocusAsserter assertOidDifferentThan(String oid) { + super.assertOidDifferentThan(oid); + return this; + } + + @Override + public FocusAsserter assertName() { + super.assertName(); + return this; + } + + @Override + public FocusAsserter assertName(String expectedOrig) { + super.assertName(expectedOrig); + return this; + } + + @Override + public FocusAsserter assertLifecycleState(String expected) { + super.assertLifecycleState(expected); + return this; + } + + @Override + public FocusAsserter assertActiveLifecycleState() { + super.assertActiveLifecycleState(); + return this; + } + + public FocusAsserter assertAdministrativeStatus(ActivationStatusType expected) { + ActivationType activation = getActivation(); + if (activation == null) { + if (expected == null) { + return this; + } else { + fail("No activation in "+desc()); + } + } + assertEquals("Wrong activation administrativeStatus in "+desc(), expected, activation.getAdministrativeStatus()); + return this; + } + + private ActivationType getActivation() { + return getObject().asObjectable().getActivation(); + } + + public FocusAsserter display() { + super.display(); + return this; + } + + public FocusAsserter display(String message) { + super.display(message); + return this; + } + + public FocusAsserter assertLinks(int expected) { + PrismReference linkRef = getObject().findReference(FocusType.F_LINK_REF); + if (linkRef == null) { + assertTrue("Expected "+expected+" links, but there is no linkRef in "+desc(), expected == 0); + return this; + } + assertEquals("Wrong number of links in " + desc(), expected, linkRef.size()); + return this; + } + + public ShadowReferenceAsserter> singleLink() { + PrismReference linkRef = getObject().findReference(FocusType.F_LINK_REF); + if (linkRef == null) { + fail("Expected single link, but is no linkRef in "+desc()); + return null; // not reached + } + assertEquals("Wrong number of links in " + desc(), 1, linkRef.size()); + ShadowReferenceAsserter> asserter = new ShadowReferenceAsserter<>(linkRef.getValue(0), this, "link in "+desc()); + copySetupTo(asserter); + return asserter; + } + + public FocusAsserter assertHasProjectionOnResource(String resourceOid) throws ObjectNotFoundException, SchemaException { + PrismObject shadow = findProjectionOnResource(resourceOid); + assertNotNull("Projection for resource "+resourceOid+" not found in "+desc(), shadow); + return this; + } + + public > ShadowAsserter projectionOnResource(String resourceOid) throws ObjectNotFoundException, SchemaException { + PrismObject shadow = findProjectionOnResource(resourceOid); + assertNotNull("Projection for resource "+resourceOid+" not found in "+desc(), shadow); + ShadowAsserter asserter = new ShadowAsserter(shadow, (A)this, "projection of "+desc()); + copySetupTo(asserter); + return asserter; + } + + private PrismObject findProjectionOnResource(String resourceOid) throws ObjectNotFoundException, SchemaException { + F focusType = getObject().asObjectable(); + for (PrismObject shadow: getLinkTargets()) { + if (resourceOid.equals(shadow.asObjectable().getResourceRef().getOid())) { + return shadow; + } + } + return null; + } + + private List> getLinkTargets() throws ObjectNotFoundException, SchemaException { + F focusType = getObject().asObjectable(); + List> shadows = new ArrayList<>(); + for (ObjectReferenceType linkRefType: focusType.getLinkRef()) { + String linkTargetOid = linkRefType.getOid(); + assertFalse("No linkRef oid in "+desc(), StringUtils.isBlank(linkTargetOid)); + shadows.add(getLinkTarget(linkTargetOid)); + } + return shadows; + } + + private PrismObject getLinkTarget(String oid) throws ObjectNotFoundException, SchemaException { + PrismObject shadow = projectionCache.get(oid); + if (shadow == null) { + shadow = resolveObject(ShadowType.class, oid); + projectionCache.put(oid, shadow); + } + return shadow; + } +} diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ObjectDeltaAsserter.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ObjectDeltaAsserter.java index 69eadb30545..405ddb19c29 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ObjectDeltaAsserter.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ObjectDeltaAsserter.java @@ -80,7 +80,7 @@ public ObjectDeltaAsserter assertChangeType(ChangeType expected) { return this; } - private String desc() { + protected String desc() { return descWithDetails(delta); } } diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ObjectDeltaTypeAsserter.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ObjectDeltaTypeAsserter.java index f2d3c1278c8..92e036c7ef4 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ObjectDeltaTypeAsserter.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ObjectDeltaTypeAsserter.java @@ -78,7 +78,7 @@ public ObjectDeltaTypeAsserter assertChangeType(ChangeTypeType expected) { return this; } - private String desc() { + protected String desc() { return descWithDetails(delta); } diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ObjectReferenceAsserter.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ObjectReferenceAsserter.java new file mode 100644 index 00000000000..135b10aaba9 --- /dev/null +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ObjectReferenceAsserter.java @@ -0,0 +1,118 @@ +/** + * Copyright (c) 2018 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.evolveum.midpoint.test.asserter; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.assertTrue; + +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismReferenceValue; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.schema.DeltaConvertor; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.test.IntegrationTestTools; +import com.evolveum.midpoint.test.util.TestUtil; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationResultStatusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.PendingOperationExecutionStatusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.PendingOperationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; +import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; + +/** + * @author semancik + * + */ +public class ObjectReferenceAsserter extends AbstractAsserter { + + final private PrismReferenceValue refVal; + final private Class defaultTargetTypeClass; + + public ObjectReferenceAsserter(PrismReferenceValue refVal, Class defaultTargetTypeClass) { + super(); + this.refVal = refVal; + this.defaultTargetTypeClass = defaultTargetTypeClass; + } + + public ObjectReferenceAsserter(PrismReferenceValue refVal, Class defaultTargetTypeClass, String detail) { + super(detail); + this.refVal = refVal; + this.defaultTargetTypeClass = defaultTargetTypeClass; + } + + public ObjectReferenceAsserter(PrismReferenceValue refVal, Class defaultTargetTypeClass, R returnAsserter, String detail) { + super(returnAsserter, detail); + this.refVal = refVal; + this.defaultTargetTypeClass = defaultTargetTypeClass; + } + + protected PrismReferenceValue getRefVal() { + return refVal; + } + + public ObjectReferenceAsserter assertOid() { + assertNotNull("No OID in "+desc(), refVal.getOid()); + return this; + } + + public ObjectReferenceAsserter assertOid(String expected) { + assertEquals("Wrong OID in "+desc(), expected, refVal.getOid()); + return this; + } + + public PrismObjectAsserter> object() { + return new PrismObjectAsserter<>((PrismObject)refVal.getObject(), this, "object in "+desc()); + } + + public PrismObjectAsserter> resolveTarget() throws ObjectNotFoundException, SchemaException { + PrismObject object = resolveTargetObject(); + return new PrismObjectAsserter<>(object, this, "object resolved from "+desc()); + } + + protected PrismObject resolveTargetObject() throws ObjectNotFoundException, SchemaException { + return resolveObject(getObjectTypeClass(), refVal.getOid()); + } + + private Class getObjectTypeClass() { + QName targetType = refVal.getTargetType(); + if (targetType == null) { + return defaultTargetTypeClass; + } + return (Class) ObjectTypes.getObjectTypeFromTypeQName(targetType).getClassDefinition(); + } + + protected String desc() { + return descWithDetails(refVal); + } + + public ObjectReferenceAsserter display() { + display(desc()); + return this; + } + + public ObjectReferenceAsserter display(String message) { + IntegrationTestTools.display(message, refVal); + return this; + } +} diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/PendingOperationAsserter.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/PendingOperationAsserter.java index 9e6c52da04c..db91ce8debc 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/PendingOperationAsserter.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/PendingOperationAsserter.java @@ -106,7 +106,7 @@ public ObjectDeltaTypeAsserter> delta() { return new ObjectDeltaTypeAsserter<>(pendingOperation.getDelta(), this, "delta in "+desc()); } - private String desc() { + protected String desc() { return descWithDetails("pending operation "+operationDesc+" in "+pendingOperationsAsserter.getShadow()); } diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/PendingOperationsAsserter.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/PendingOperationsAsserter.java index ee7a81a969d..c53f200e419 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/PendingOperationsAsserter.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/PendingOperationsAsserter.java @@ -24,6 +24,7 @@ import java.util.List; import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.xml.ns._public.common.common_3.PendingOperationExecutionStatusType; import com.evolveum.midpoint.xml.ns._public.common.common_3.PendingOperationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; import com.evolveum.prism.xml.ns._public.types_3.ChangeTypeType; @@ -47,7 +48,7 @@ public PendingOperationsAsserter(ShadowAsserter shadowAsserter, String detail } public static PendingOperationsAsserter forShadow(PrismObject shadow) { - return new PendingOperationsAsserter(ShadowAsserter.forShadow(shadow)); + return new PendingOperationsAsserter<>(ShadowAsserter.forShadow(shadow)); } List getOperations() { @@ -60,7 +61,9 @@ public PendingOperationsAsserter assertOperations(int expectedNumber) { } PendingOperationAsserter forOperation(PendingOperationType operation) { - return new PendingOperationAsserter<>(this, operation, idToString(operation.getId()), getDetails()); + PendingOperationAsserter asserter = new PendingOperationAsserter<>(this, operation, idToString(operation.getId()), getDetails()); + copySetupTo(asserter); + return asserter; } private String idToString(Long id) { @@ -98,7 +101,21 @@ public PendingOperationAsserter deleteOperation() { .changeType(ChangeTypeType.DELETE) .find(); } + + public PendingOperationsAsserter assertUnfinishedOperation() { + for (PendingOperationType operation: getOperations()) { + if (isUnfinished(operation)) { + return this; + } + } + fail("No unfinished operations in "+desc()); + return null; // not reached + } + private boolean isUnfinished(PendingOperationType operation) { + return operation.getExecutionStatus() != PendingOperationExecutionStatusType.COMPLETED; + } + public PendingOperationFinder by() { return new PendingOperationFinder<>(this); } @@ -111,4 +128,10 @@ PrismObject getShadow() { public ShadowAsserter end() { return shadowAsserter; } + + @Override + protected String desc() { + return descWithDetails("pending operations of "+shadowAsserter.getObject()); + } + } diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/PrismObjectAsserter.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/PrismObjectAsserter.java index 932008ca703..1ceecec2eee 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/PrismObjectAsserter.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/PrismObjectAsserter.java @@ -23,7 +23,11 @@ import java.util.List; +import javax.xml.namespace.QName; + import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismProperty; +import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.prism.util.PrismAsserts; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.test.IntegrationTestTools; @@ -118,5 +122,11 @@ public PrismObjectAsserter display(String message) { IntegrationTestTools.display(message, object); return this; } + + protected void assertPolyStringProperty(QName propName, String expectedOrig) { + PrismProperty prop = getObject().findProperty(propName); + assertNotNull("No "+propName.getLocalPart()+" in "+desc(), prop); + PrismAsserts.assertEqualsPolyString("Wrong "+propName.getLocalPart()+" in "+desc(), expectedOrig, prop.getRealValue()); + } } diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ShadowAsserter.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ShadowAsserter.java index f587c779d35..faa4fe44951 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ShadowAsserter.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ShadowAsserter.java @@ -177,11 +177,21 @@ public ShadowAsserter assertIsExists(Boolean expected) { } public PendingOperationsAsserter pendingOperations() { - return new PendingOperationsAsserter<>(this, getDetails()); + PendingOperationsAsserter asserter = new PendingOperationsAsserter<>(this, getDetails()); + copySetupTo(asserter); + return asserter; + } + + public ShadowAsserter hasUnfinishedPendingOperations() { + pendingOperations() + .assertUnfinishedOperation(); + return this; } public ShadowAttributesAsserter attributes() { - return new ShadowAttributesAsserter<>(this, getDetails()); + ShadowAttributesAsserter asserter = new ShadowAttributesAsserter<>(this, getDetails()); + copySetupTo(asserter); + return asserter; } public ShadowAsserter assertNoLegacyConsistency() { 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 4594fe09c90..8ccb7d54723 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 @@ -153,7 +153,7 @@ private PrismProperty findAttribute(QName attrName) { return getAttributes().findProperty(attrName); } - private String desc() { + protected String desc() { return descWithDetails(getShadow()); } diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ShadowReferenceAsserter.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ShadowReferenceAsserter.java new file mode 100644 index 00000000000..6304f03629b --- /dev/null +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/ShadowReferenceAsserter.java @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2018 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.evolveum.midpoint.test.asserter; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.assertTrue; + +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismReferenceValue; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.schema.DeltaConvertor; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.test.IntegrationTestTools; +import com.evolveum.midpoint.test.util.TestUtil; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationResultStatusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.PendingOperationExecutionStatusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.PendingOperationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; +import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType; + +/** + * @author semancik + * + */ +public class ShadowReferenceAsserter extends ObjectReferenceAsserter { + + public ShadowReferenceAsserter(PrismReferenceValue refVal) { + super(refVal, ShadowType.class); + } + + public ShadowReferenceAsserter(PrismReferenceValue refVal, String detail) { + super(refVal, ShadowType.class, detail); + } + + public ShadowReferenceAsserter(PrismReferenceValue refVal, R returnAsserter, String detail) { + super(refVal, ShadowType.class, returnAsserter, detail); + } + + @Override + public ShadowReferenceAsserter assertOid() { + super.assertOid(); + return this; + } + + public ShadowReferenceAsserter assertOid(String expected) { + super.assertOid(expected); + return this; + } + + public ShadowAsserter> shadow() { + ShadowAsserter> asserter = new ShadowAsserter<>((PrismObject)getRefVal().getObject(), this, "shadow in reference "+desc()); + copySetupTo(asserter); + return asserter; + } + + @Override + public ShadowAsserter> resolveTarget() + throws ObjectNotFoundException, SchemaException { + PrismObject object = resolveTargetObject(); + return new ShadowAsserter<>(object, this, "object resolved from "+desc()); + } + +} diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/UserAsserter.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/UserAsserter.java new file mode 100644 index 00000000000..74ccb87f67d --- /dev/null +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/asserter/UserAsserter.java @@ -0,0 +1,162 @@ +/** + * Copyright (c) 2018 Evolveum + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.evolveum.midpoint.test.asserter; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertFalse; +import static org.testng.AssertJUnit.assertNotNull; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.assertTrue; + +import java.util.List; + +import javax.xml.namespace.QName; + +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.PrismReference; +import com.evolveum.midpoint.prism.util.PrismAsserts; +import com.evolveum.midpoint.util.exception.ObjectNotFoundException; +import com.evolveum.midpoint.util.exception.SchemaException; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationStatusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.PendingOperationType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowKindType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType; + +/** + * @author semancik + * + */ +public class UserAsserter extends FocusAsserter { + + public UserAsserter(PrismObject focus) { + super(focus); + } + + public UserAsserter(PrismObject focus, String details) { + super(focus, details); + } + + public UserAsserter(PrismObject focus, R returnAsserter, String details) { + super(focus, returnAsserter, details); + } + + public static UserAsserter forUser(PrismObject focus) { + return new UserAsserter<>(focus); + } + + public static UserAsserter forUser(PrismObject focus, String details) { + return new UserAsserter<>(focus, details); + } + + @Override + public UserAsserter assertOid() { + super.assertOid(); + return this; + } + + @Override + public UserAsserter assertOid(String expected) { + super.assertOid(expected); + return this; + } + + @Override + public UserAsserter assertOidDifferentThan(String oid) { + super.assertOidDifferentThan(oid); + return this; + } + + @Override + public UserAsserter assertName() { + super.assertName(); + return this; + } + + @Override + public UserAsserter assertName(String expectedOrig) { + super.assertName(expectedOrig); + return this; + } + + @Override + public UserAsserter assertLifecycleState(String expected) { + super.assertLifecycleState(expected); + return this; + } + + @Override + public UserAsserter assertActiveLifecycleState() { + super.assertActiveLifecycleState(); + return this; + } + + public UserAsserter assertAdministrativeStatus(ActivationStatusType expected) { + ActivationType activation = getActivation(); + if (activation == null) { + if (expected == null) { + return this; + } else { + fail("No activation in "+desc()); + } + } + assertEquals("Wrong activation administrativeStatus in "+desc(), expected, activation.getAdministrativeStatus()); + return this; + } + + private ActivationType getActivation() { + return getObject().asObjectable().getActivation(); + } + + public UserAsserter display() { + super.display(); + return this; + } + + public UserAsserter display(String message) { + super.display(message); + return this; + } + + @Override + public UserAsserter assertLinks(int expected) { + super.assertLinks(expected); + return this; + } + + public UserAsserter assertFullName(String expectedOrig) { + assertPolyStringProperty(UserType.F_FULL_NAME, expectedOrig); + return this; + } + + public UserAsserter assertLocality(String expectedOrig) { + assertPolyStringProperty(UserType.F_LOCALITY, expectedOrig); + return this; + } + + @Override + public UserAsserter assertHasProjectionOnResource(String resourceOid) throws ObjectNotFoundException, SchemaException { + super.assertHasProjectionOnResource(resourceOid); + return this; + } + + @Override + public ShadowAsserter> projectionOnResource(String resourceOid) throws ObjectNotFoundException, SchemaException { + return super.projectionOnResource(resourceOid); + } +} diff --git a/repo/repo-test-util/src/main/resources/ctx-configuration-test.xml b/repo/repo-test-util/src/main/resources/ctx-configuration-test.xml index 693dd882edd..3881ef84991 100644 --- a/repo/repo-test-util/src/main/resources/ctx-configuration-test.xml +++ b/repo/repo-test-util/src/main/resources/ctx-configuration-test.xml @@ -24,6 +24,8 @@ http://www.springframework.org/schema/context/spring-context-3.0.xsd" default-lazy-init="false" default-autowire="byName"> + +