diff --git a/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismProperty.java b/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismProperty.java index 8105fbb3c8d..40fddc160fc 100644 --- a/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismProperty.java +++ b/infra/prism/src/main/java/com/evolveum/midpoint/prism/PrismProperty.java @@ -394,6 +394,9 @@ public PropertyDelta diff(PrismProperty other, boolean ignoreMetadata, boo public static PropertyDelta diff(PrismProperty a, PrismProperty b) { if (a == null) { + if (b == null) { + return null; + } PropertyDelta delta = b.createDelta(); delta.addValuesToAdd(PrismValue.cloneCollection(b.getValues())); return delta; 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 2c6ec6a451f..f13a16e1134 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 @@ -106,7 +106,7 @@ public class GetOperationOptions implements Serializable, Cloneable { * don't log it. In other cases, error in logs may lead to misleading * information.. */ - Boolean allowNotFound; + private Boolean allowNotFound; /** * Return read-only object. The returned object will be only read by the client. The client will not modify it. @@ -115,6 +115,14 @@ public class GetOperationOptions implements Serializable, Cloneable { * at all times when the client do not plan to modify the returned object. */ private Boolean readOnly; + + /** + * Requirement how stale or fresh the retrieved data should be. It specifies maximum age of the value in millisecods. + * 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. + */ + private Long staleness; public RetrieveOption getRetrieve() { return retrieve; @@ -394,6 +402,36 @@ public static boolean isReadOnly(GetOperationOptions options) { return options.readOnly; } + public Long getStaleness() { + return staleness; + } + + public void setStaleness(Long staleness) { + this.staleness = staleness; + } + + public static GetOperationOptions createStaleness(Long staleness) { + GetOperationOptions opts = new GetOperationOptions(); + opts.setStaleness(staleness); + return opts; + } + + public static GetOperationOptions createMaxStaleness() { + GetOperationOptions opts = new GetOperationOptions(); + opts.setStaleness(Long.MAX_VALUE); + return opts; + } + + public static long getStaleness(GetOperationOptions options) { + if (options == null) { + return 0L; + } + if (options.getStaleness() == null) { + return 0L; + } + return options.getStaleness(); + } + public RelationalValueSearchQuery getRelationalValueSearchQuery() { return relationalValueSearchQuery; } @@ -402,7 +440,7 @@ public void setRelationalValueSearchQuery(RelationalValueSearchQuery relationalV this.relationalValueSearchQuery = relationalValueSearchQuery; } - @Override + @Override public int hashCode() { final int prime = 31; int result = 1; @@ -410,60 +448,102 @@ public int hashCode() { result = prime * result + ((doNotDiscovery == null) ? 0 : doNotDiscovery.hashCode()); result = prime * result + ((noFetch == null) ? 0 : noFetch.hashCode()); result = prime * result + ((raw == null) ? 0 : raw.hashCode()); + result = prime * result + ((readOnly == null) ? 0 : readOnly.hashCode()); result = prime * result + ((relationalValueSearchQuery == null) ? 0 : relationalValueSearchQuery.hashCode()); result = prime * result + ((resolve == null) ? 0 : resolve.hashCode()); result = prime * result + ((resolveNames == null) ? 0 : resolveNames.hashCode()); result = prime * result + ((retrieve == null) ? 0 : retrieve.hashCode()); + result = prime * result + ((staleness == null) ? 0 : staleness.hashCode()); + result = prime * result + ((tolerateRawData == null) ? 0 : tolerateRawData.hashCode()); return result; } @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - if (obj == null) + } + if (obj == null) { return false; - if (getClass() != obj.getClass()) + } + if (getClass() != obj.getClass()) { return false; + } GetOperationOptions other = (GetOperationOptions) obj; if (allowNotFound == null) { - if (other.allowNotFound != null) + if (other.allowNotFound != null) { return false; - } else if (!allowNotFound.equals(other.allowNotFound)) + } + } else if (!allowNotFound.equals(other.allowNotFound)) { return false; + } if (doNotDiscovery == null) { - if (other.doNotDiscovery != null) + if (other.doNotDiscovery != null) { return false; - } else if (!doNotDiscovery.equals(other.doNotDiscovery)) + } + } else if (!doNotDiscovery.equals(other.doNotDiscovery)) { return false; + } if (noFetch == null) { - if (other.noFetch != null) + if (other.noFetch != null) { return false; - } else if (!noFetch.equals(other.noFetch)) + } + } else if (!noFetch.equals(other.noFetch)) { return false; + } if (raw == null) { - if (other.raw != null) + if (other.raw != null) { return false; - } else if (!raw.equals(other.raw)) + } + } else if (!raw.equals(other.raw)) { return false; + } + if (readOnly == null) { + if (other.readOnly != null) { + return false; + } + } else if (!readOnly.equals(other.readOnly)) { + return false; + } if (relationalValueSearchQuery == null) { - if (other.relationalValueSearchQuery != null) + if (other.relationalValueSearchQuery != null) { return false; - } else if (!relationalValueSearchQuery.equals(other.relationalValueSearchQuery)) + } + } else if (!relationalValueSearchQuery.equals(other.relationalValueSearchQuery)) { return false; + } if (resolve == null) { - if (other.resolve != null) + if (other.resolve != null) { return false; - } else if (!resolve.equals(other.resolve)) + } + } else if (!resolve.equals(other.resolve)) { return false; + } if (resolveNames == null) { - if (other.resolveNames != null) + if (other.resolveNames != null) { + return false; + } + } else if (!resolveNames.equals(other.resolveNames)) { + return false; + } + if (retrieve != other.retrieve) { + return false; + } + if (staleness == null) { + if (other.staleness != null) { return false; - } else if (!resolveNames.equals(other.resolveNames)) + } + } else if (!staleness.equals(other.staleness)) { return false; - if (retrieve != other.retrieve) + } + if (tolerateRawData == null) { + if (other.tolerateRawData != null) { + return false; + } + } else if (!tolerateRawData.equals(other.tolerateRawData)) { return false; + } return true; } @@ -477,6 +557,7 @@ public GetOperationOptions clone() { clone.retrieve = this.retrieve; clone.allowNotFound = this.allowNotFound; clone.readOnly = this.readOnly; + clone.staleness = this.staleness; if (this.relationalValueSearchQuery != null) { clone.relationalValueSearchQuery = this.relationalValueSearchQuery.clone(); } @@ -494,6 +575,7 @@ public String toString() { appendVal(sb, "retrieve", retrieve); appendFlag(sb, "allowNotFound", allowNotFound); appendFlag(sb, "readOnly", readOnly); + appendVal(sb, "staleness", staleness); appendVal(sb, "relationalValueSearchQuery", relationalValueSearchQuery); if (sb.charAt(sb.length() - 1) == ',') { sb.deleteCharAt(sb.length() - 1); 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 d2d1e66b0ba..b44d4e06b22 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 @@ -20,6 +20,7 @@ import java.util.Iterator; import java.util.List; +import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; import com.evolveum.midpoint.prism.Item; @@ -30,6 +31,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import com.evolveum.midpoint.common.Clock; import com.evolveum.midpoint.common.refinery.RefinedAssociationDefinition; import com.evolveum.midpoint.common.refinery.RefinedAttributeDefinition; import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; @@ -68,6 +70,7 @@ import com.evolveum.midpoint.prism.query.SubstringFilter; import com.evolveum.midpoint.prism.query.ValueFilter; import com.evolveum.midpoint.prism.util.PrismUtil; +import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.provisioning.api.ChangeNotificationDispatcher; import com.evolveum.midpoint.provisioning.api.GenericConnectorException; import com.evolveum.midpoint.provisioning.api.ProvisioningOperationOptions; @@ -119,7 +122,9 @@ import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.AvailabilityStatusType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.CachingMetadataType; import com.evolveum.midpoint.xml.ns._public.common.common_3.FailedOperationTypeType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.MetadataType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationProvisioningScriptsType; @@ -158,6 +163,9 @@ public abstract class ShadowCache { @Autowired(required = true) private ResourceManager resourceManager; + + @Autowired(required = true) + private Clock clock; @Autowired(required = true) private PrismContext prismContext; @@ -235,6 +243,11 @@ public PrismObject getShadow(String oid, PrismObject rep parentResult.recordFatalError("Provided OID is not equal to OID of repository shadow"); throw new IllegalArgumentException("Provided OID is not equal to OID of repository shadow"); } + + if (canReturnCached(options, repositoryShadow)) { + applyDefinition(repositoryShadow, parentResult); + return repositoryShadow; + } ProvisioningContext ctx = ctxFactory.create(repositoryShadow, task, parentResult); try { @@ -347,6 +360,31 @@ public PrismObject getShadow(String oid, PrismObject rep } + private boolean canReturnCached(Collection> options, PrismObject repositoryShadow) throws ConfigurationException { + long stalenessOption = GetOperationOptions.getStaleness(SelectorOptions.findRootOptions(options)); + if (stalenessOption == 0L) { + return false; + } + CachingMetadataType cachingMetadata = repositoryShadow.asObjectable().getCachingMetadata(); + if (cachingMetadata == null) { + if (stalenessOption == Long.MAX_VALUE) { + // We must return cached version but there is no cached version. + throw new ConfigurationException("Cached version of "+repositoryShadow+" requested, but there is no cached value"); + } + return false; + } + if (stalenessOption == Long.MAX_VALUE) { + return true; + } + + XMLGregorianCalendar retrievalTimestamp = cachingMetadata.getRetrievalTimestamp(); + if (retrievalTimestamp == null) { + return false; + } + long retrievalTimestampMillis = XmlTypeConverter.toMillis(retrievalTimestamp); + return (clock.currentTimeMillis() - retrievalTimestampMillis < stalenessOption); + } + private boolean isCompensate(GetOperationOptions rootOptions) { return !GetOperationOptions.isDoNotDiscovery(rootOptions); } 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 0c99c01f8d1..e26a491e126 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 @@ -814,6 +814,8 @@ public PrismObject createRepositoryShadow(ProvisioningContext ctx, P repoShadowType.setCachingMetadata(null); + ProvisioningUtil.cleanupShadowActivation(repoShadowType); + } else if (cachingStrategy == CachingStategyType.PASSIVE) { // Do not need to clear anything. Just store all attributes and add metadata. CachingMetadataType cachingMetadata = new CachingMetadataType(); @@ -857,8 +859,6 @@ public PrismObject createRepositoryShadow(ProvisioningContext ctx, P normalizeAttributes(repoShadow, ctx.getObjectClassDefinition()); - ProvisioningUtil.cleanupShadowActivation(repoShadowType); - return repoShadow; } @@ -891,6 +891,9 @@ public Collection updateShadow(ProvisioningContext ctx, PrismObject updateShadow(ProvisioningContext ctx, PrismObject } } else if (cachingStrategy == CachingStategyType.PASSIVE) { + + compareUpdateProperty(shadowDelta, SchemaConstants.PATH_ACTIVATION_ADMINISTRATIVE_STATUS, currentResourceShadow, oldRepoShadow); + compareUpdateProperty(shadowDelta, SchemaConstants.PATH_ACTIVATION_VALID_FROM, currentResourceShadow, oldRepoShadow); + compareUpdateProperty(shadowDelta, SchemaConstants.PATH_ACTIVATION_VALID_TO, currentResourceShadow, oldRepoShadow); + compareUpdateProperty(shadowDelta, SchemaConstants.PATH_ACTIVATION_LOCKOUT_STATUS, currentResourceShadow, oldRepoShadow); + CachingMetadataType cachingMetadata = new CachingMetadataType(); cachingMetadata.setRetrievalTimestamp(clock.currentTimeXMLGregorianCalendar()); shadowDelta.addModificationReplaceProperty(ShadowType.F_CACHING_METADATA, cachingMetadata); @@ -1041,6 +1050,16 @@ public PrismObject updateShadow(ProvisioningContext ctx, PrismObject } } + private void compareUpdateProperty(ObjectDelta shadowDelta, + ItemPath itemPath, PrismObject currentResourceShadow, PrismObject oldRepoShadow) { + PrismProperty currentProperty = currentResourceShadow.findProperty(itemPath); + PrismProperty oldProperty = oldRepoShadow.findProperty(itemPath); + PropertyDelta itemDelta = PrismProperty.diff(oldProperty, currentProperty); + if (itemDelta != null && !itemDelta.isEmpty()) { + shadowDelta.addModification(itemDelta); + } + } + /** * Re-reads the shadow, re-evaluates the identifiers and stored values, updates them if necessary. Returns * fixed shadow. diff --git a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/dummy/TestDummy.java b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/dummy/TestDummy.java index 086eccf554b..04760d5673d 100644 --- a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/dummy/TestDummy.java +++ b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/dummy/TestDummy.java @@ -1186,6 +1186,7 @@ protected void checkRepoAccountShadowWillBasic(PrismObject accountRe protected void checkRepoAccountShadowWill(PrismObject accountRepo, XMLGregorianCalendar start, XMLGregorianCalendar end) { checkRepoAccountShadowWillBasic(accountRepo, start, end, 2); + assertRepoShadowCacheActivation(accountRepo, null); } @Test @@ -1299,7 +1300,7 @@ protected void assertRepoCachingMetadata(PrismObject shadowRepo, XML assertNull("Unexpected caching metadata in "+shadowRepo, shadowRepo.asObjectable().getCachingMetadata()); } - private void checkCachingMetadata(ShadowType shadow, XMLGregorianCalendar startTs, + protected void checkCachingMetadata(ShadowType shadow, XMLGregorianCalendar startTs, XMLGregorianCalendar endTs) { CachingMetadataType cachingMetadata = shadow.getCachingMetadata(); assertNotNull("No caching metadata in "+shadow, cachingMetadata); @@ -1397,6 +1398,7 @@ public void test106GetModifiedAccount() throws Exception { DummyAccount accountWill = getDummyAccountAssert(transformNameFromResource(ACCOUNT_WILL_USERNAME), willIcfUid); accountWill.replaceAttributeValue(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_TITLE_NAME, "Pirate"); accountWill.replaceAttributeValue(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME, "Black Pearl"); + accountWill.setEnabled(false); XMLGregorianCalendar startTs = clock.currentTimeXMLGregorianCalendar(); @@ -1418,7 +1420,6 @@ public void test106GetModifiedAccount() throws Exception { assertNotNull("No dummy account", shadow); - checkAccountShadow(shadow, result); assertAttribute(shadow, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_TITLE_NAME, "Pirate"); assertAttribute(shadow, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME, "Black Pearl"); assertAttribute(shadow, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_WEAPON_NAME, "Sword", "LOVE"); @@ -1434,6 +1435,7 @@ public void test106GetModifiedAccount() throws Exception { // MID-3484 // assertRepoShadowCachedAttributeValue(shadowRepo, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_WEAPON_NAME, "Sword", "LOVE"); assertRepoShadowCachedAttributeValue(shadowRepo, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOOT_NAME, 42); + assertRepoShadowCacheActivation(shadowRepo, ActivationStatusType.DISABLED); checkConsistency(shadow.asPrismObject()); @@ -1443,13 +1445,16 @@ public void test106GetModifiedAccount() throws Exception { } /** - * Make a native modification to an account and read it from the cache. Make sure that - * cached data are returned and there is no read from the resource. + * Make a native modification to an account and read it with max staleness option. + * As there is no caching enabled this should throw an error. + * + * Note: This test is overridden in TestDummyCaching + * * MID-3481 */ @Test - public void test107GetModifiedAccountFromCache() throws Exception { - final String TEST_NAME = "test107GetModifiedAccountFromCache"; + public void test107AGetModifiedAccountFromCacheMax() throws Exception { + final String TEST_NAME = "test107AGetModifiedAccountFromCacheMax"; TestUtil.displayTestTile(TEST_NAME); // GIVEN OperationResult result = new OperationResult(TestDummy.class.getName() + "." + TEST_NAME); @@ -1458,14 +1463,103 @@ public void test107GetModifiedAccountFromCache() throws Exception { DummyAccount accountWill = getDummyAccountAssert(transformNameFromResource(ACCOUNT_WILL_USERNAME), willIcfUid); accountWill.replaceAttributeValue(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_TITLE_NAME, "Nice Pirate"); accountWill.replaceAttributeValue(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME, "Interceptor"); + accountWill.setEnabled(true); + + Collection> options = + SelectorOptions.createCollection(GetOperationOptions.createMaxStaleness()); + + XMLGregorianCalendar startTs = clock.currentTimeXMLGregorianCalendar(); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + + try { + + ShadowType shadow = provisioningService.getObject(ShadowType.class, ACCOUNT_WILL_OID, options, null, + result).asObjectable(); + + AssertJUnit.fail("Unexpected success"); + } catch (ConfigurationException e) { + // Caching is disabled, this is expected. + TestUtil.displayThen(TEST_NAME); + display("Expected exception", e); + result.computeStatus(); + TestUtil.assertFailure(result); + } + + PrismObject shadowRepo = repositoryService.getObject(ShadowType.class, ACCOUNT_WILL_OID, null, result); + checkRepoAccountShadowWillBasic(shadowRepo, null, startTs, null); + + assertRepoShadowCachedAttributeValue(shadowRepo, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_TITLE_NAME, "Pirate"); + assertRepoShadowCachedAttributeValue(shadowRepo, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME, "Black Pearl"); + // MID-3484 +// assertRepoShadowCachedAttributeValue(shadowRepo, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_WEAPON_NAME, "Sword", "LOVE"); + assertRepoShadowCachedAttributeValue(shadowRepo, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOOT_NAME, 42); + assertRepoShadowCacheActivation(shadowRepo, ActivationStatusType.DISABLED); + assertShadowFetchOperationCountIncrement(0); - // TODO - // TODO - // TODO - // TODO - // TODO - // TODO + assertSteadyResource(); + } + + /** + * Staleness of one millisecond is too small for the cache to work. + * Fresh data should be returned - both in case the cache is enabled and disabled. + * MID-3481 + */ + @Test + public void test108GetAccountLowStaleness() throws Exception { + final String TEST_NAME = "test106GetModifiedAccount"; + TestUtil.displayTestTile(TEST_NAME); + // GIVEN + OperationResult result = new OperationResult(TestDummy.class.getName() + "." + TEST_NAME); + rememberShadowFetchOperationCount(); + + Collection> options = + SelectorOptions.createCollection(GetOperationOptions.createStaleness(1L)); + + XMLGregorianCalendar startTs = clock.currentTimeXMLGregorianCalendar(); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + ShadowType shadow = provisioningService.getObject(ShadowType.class, ACCOUNT_WILL_OID, options, null, + result).asObjectable(); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + display("getObject result", result); + TestUtil.assertSuccess(result); + assertShadowFetchOperationCountIncrement(1); + + XMLGregorianCalendar endTs = clock.currentTimeXMLGregorianCalendar(); + + display("Retrieved account shadow", shadow); + + assertNotNull("No dummy account", shadow); + + checkAccountShadow(shadow, result); + assertAttribute(shadow, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_TITLE_NAME, "Nice Pirate"); + assertAttribute(shadow, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME, "Interceptor"); + assertAttribute(shadow, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_WEAPON_NAME, "Sword", "LOVE"); + assertAttribute(shadow, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOOT_NAME, 42); + Collection> attributes = ShadowUtil.getAttributes(shadow); + assertEquals("Unexpected number of attributes", 7, attributes.size()); + + PrismObject shadowRepo = repositoryService.getObject(ShadowType.class, ACCOUNT_WILL_OID, null, result); + checkRepoAccountShadowWillBasic(shadowRepo, startTs, endTs, null); + + assertRepoShadowCachedAttributeValue(shadowRepo, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_TITLE_NAME, "Nice Pirate"); + assertRepoShadowCachedAttributeValue(shadowRepo, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME, "Interceptor"); + // MID-3484 +// assertRepoShadowCachedAttributeValue(shadowRepo, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_WEAPON_NAME, "Sword", "LOVE"); + assertRepoShadowCachedAttributeValue(shadowRepo, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOOT_NAME, 42); + + checkConsistency(shadow.asPrismObject()); + + checkCachingMetadata(shadow, startTs, endTs); + + assertSteadyResource(); } /** @@ -1485,6 +1579,7 @@ public void test109ModifiedAccountCleanup() throws Exception { // Modify this back so won't break subsequent tests accountWill.replaceAttributeValue(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME, "Flying Dutchman"); accountWill.replaceAttributeValues(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_TITLE_NAME); + accountWill.setEnabled(true); XMLGregorianCalendar startTs = clock.currentTimeXMLGregorianCalendar(); @@ -5283,7 +5378,7 @@ public void test999Shutdown() throws Exception { dummyResource.assertNoConnections(); } - private void checkAccountShadow(ShadowType shadow, OperationResult parentResult) throws SchemaException { + protected void checkAccountShadow(ShadowType shadow, OperationResult parentResult) throws SchemaException { checkAccountShadow(shadow, parentResult, true); } @@ -5379,4 +5474,13 @@ protected void assertRepoShadowCachedAttributeValue(PrismObject new QName(ResourceTypeUtil.getResourceNamespace(resource), attrName))); } + protected void assertRepoShadowCacheActivation(PrismObject shadowRepo, ActivationStatusType expectedAdministrativeStatus) { + ActivationType activationType = shadowRepo.asObjectable().getActivation(); + if (activationType == null) { + return; + } + ActivationStatusType administrativeStatus = activationType.getAdministrativeStatus(); + assertNull("Unexpected activation administrativeStatus in repo shadow "+shadowRepo+": "+administrativeStatus, administrativeStatus); + } + } diff --git a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/dummy/TestDummyCaching.java b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/dummy/TestDummyCaching.java index 37dea4cb71f..64d6c41e9b1 100644 --- a/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/dummy/TestDummyCaching.java +++ b/provisioning/provisioning-impl/src/test/java/com/evolveum/midpoint/provisioning/impl/dummy/TestDummyCaching.java @@ -49,6 +49,7 @@ import com.evolveum.midpoint.provisioning.impl.ProvisioningTestUtil; import com.evolveum.midpoint.provisioning.ucf.impl.ConnectorFactoryIcfImpl; import com.evolveum.midpoint.schema.GetOperationOptions; +import com.evolveum.midpoint.schema.SelectorOptions; import com.evolveum.midpoint.schema.processor.ResourceAttribute; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.ShadowUtil; @@ -57,6 +58,8 @@ import com.evolveum.midpoint.test.IntegrationTestTools; import com.evolveum.midpoint.test.util.TestUtil; import com.evolveum.midpoint.util.DOMUtil; +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.CachingMetadataType; import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationProvisioningScriptsType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowKindType; @@ -84,6 +87,77 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti protected File getResourceDummyFilename() { return RESOURCE_DUMMY_FILE; } + + /** + * Make a native modification to an account and read it from the cache. Make sure that + * cached data are returned and there is no read from the resource. + * MID-3481 + */ + @Test + @Override + public void test107AGetModifiedAccountFromCacheMax() throws Exception { + final String TEST_NAME = "test107AGetModifiedAccountFromCacheMax"; + TestUtil.displayTestTile(TEST_NAME); + // GIVEN + OperationResult result = new OperationResult(TestDummy.class.getName() + "." + TEST_NAME); + rememberShadowFetchOperationCount(); + + DummyAccount accountWill = getDummyAccountAssert(transformNameFromResource(ACCOUNT_WILL_USERNAME), willIcfUid); + accountWill.replaceAttributeValue(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_TITLE_NAME, "Nice Pirate"); + accountWill.replaceAttributeValue(DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME, "Interceptor"); + accountWill.setEnabled(true); + + Collection> options = + SelectorOptions.createCollection(GetOperationOptions.createMaxStaleness()); + + XMLGregorianCalendar startTs = clock.currentTimeXMLGregorianCalendar(); + + // WHEN + TestUtil.displayWhen(TEST_NAME); + + ShadowType shadow = provisioningService.getObject(ShadowType.class, ACCOUNT_WILL_OID, options, null, + result).asObjectable(); + + // THEN + TestUtil.displayThen(TEST_NAME); + result.computeStatus(); + display("getObject result", result); + TestUtil.assertSuccess(result); + + assertShadowFetchOperationCountIncrement(0); + + XMLGregorianCalendar endTs = clock.currentTimeXMLGregorianCalendar(); + + display("Retrieved account shadow", shadow); + + assertNotNull("No dummy account", shadow); + + assertAttribute(shadow, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_TITLE_NAME, "Pirate"); + assertAttribute(shadow, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME, "Black Pearl"); + // MID-3484 +// assertAttribute(shadow, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_WEAPON_NAME, "Sword", "LOVE"); + assertAttribute(shadow, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOOT_NAME, 42); + Collection> attributes = ShadowUtil.getAttributes(shadow); + assertEquals("Unexpected number of attributes", 7, attributes.size()); + + PrismObject shadowRepo = repositoryService.getObject(ShadowType.class, ACCOUNT_WILL_OID, null, result); + checkRepoAccountShadowWillBasic(shadowRepo, null, startTs, null); + + assertRepoShadowCachedAttributeValue(shadowRepo, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_TITLE_NAME, "Pirate"); + assertRepoShadowCachedAttributeValue(shadowRepo, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_SHIP_NAME, "Black Pearl"); + // MID-3484 +// assertRepoShadowCachedAttributeValue(shadowRepo, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_WEAPON_NAME, "Sword", "LOVE"); + assertRepoShadowCachedAttributeValue(shadowRepo, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOOT_NAME, 42); + assertRepoShadowCacheActivation(shadowRepo, ActivationStatusType.DISABLED); + + checkConsistency(shadow.asPrismObject()); + + checkCachingMetadata(shadow, null, startTs); + + assertShadowFetchOperationCountIncrement(0); + + assertSteadyResource(); + } @Override protected void checkRepoAccountShadowWill(PrismObject shadowRepo, XMLGregorianCalendar start, XMLGregorianCalendar end) { @@ -96,6 +170,16 @@ protected void checkRepoAccountShadowWill(PrismObject shadowRepo, XM // MID-3484 // assertRepoShadowCachedAttributeValue(shadowRepo, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_WEAPON_NAME, "sword", "love"); assertRepoShadowCachedAttributeValue(shadowRepo, DummyResourceContoller.DUMMY_ACCOUNT_ATTRIBUTE_LOOT_NAME, 42); + + assertRepoShadowCacheActivation(shadowRepo, ActivationStatusType.ENABLED); + } + + @Override + protected void assertRepoShadowCacheActivation(PrismObject shadowRepo, ActivationStatusType expectedAdministrativeStatus) { + ActivationType activationType = shadowRepo.asObjectable().getActivation(); + assertNotNull("No activation in repo shadow "+shadowRepo, activationType); + ActivationStatusType administrativeStatus = activationType.getAdministrativeStatus(); + assertEquals("Wrong activation administrativeStatus in repo shadow "+shadowRepo, expectedAdministrativeStatus, administrativeStatus); } /**