Skip to content

Commit

Permalink
Extending the story tests, option to turn off explicit association re…
Browse files Browse the repository at this point in the history
…ferential integrity, some minor fixes and logging.
  • Loading branch information
semancik committed Apr 16, 2014
1 parent 699290e commit d1787c4
Show file tree
Hide file tree
Showing 17 changed files with 489 additions and 64 deletions.
Expand Up @@ -1338,10 +1338,24 @@ protected String debugName() {
}

protected String debugIdentifiers() {
return "oid=" + getOid();
return toDebugType()+":" + getOid();
}

/**
* Returns short string identification of object type. It should be in a form
* suitable for log messages. There is no requirement for the type name to be unique,
* but it rather has to be compact. E.g. short element names are preferred to long
* QNames or URIs.
* @return
*/
public String toDebugType() {
if (objectTypeClass == null) {
return "(unknown)";
}
return objectTypeClass.getSimpleName();
}

@Override
@Override
public String debugDump() {
return debugDump(0);
}
Expand Down
1 change: 1 addition & 0 deletions infra/schema/.gitignore
@@ -1,3 +1,4 @@
/target
/test-output
/target
/target
Expand Up @@ -4047,6 +4047,7 @@
<xsd:element name="direction" type="tns:ResourceObjectAssociationDirectionType" minOccurs="0"></xsd:element>
<xsd:element name="associationAttribute" type="xsd:QName" minOccurs="0"></xsd:element>
<xsd:element name="valueAttribute" type="xsd:QName" minOccurs="0"></xsd:element>
<xsd:element name="explicitReferentialIntegrity" type="xsd:boolean" minOccurs="0" default="true"></xsd:element>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
Expand Down
Expand Up @@ -553,6 +553,13 @@ public static void assertDn(SearchResultEntry response, String expected) throws
}
}

public void assertNoEntry(String dn) throws DirectoryException {
SearchResultEntry entry = fetchEntry(dn);
if (entry != null) {
AssertJUnit.fail("Found entry for dn "+dn+" while not expecting it: "+entry);
}
}

public static void assertObjectClass(SearchResultEntry response, String expected) throws DirectoryException {
Collection<String> objectClassValues = getAttributeValues(response, "objectClass");
AssertJUnit.assertTrue("Wrong objectclass for entry "+getDn(response)+", expected "+expected+" but got "+objectClassValues,
Expand All @@ -564,6 +571,22 @@ public void assertUniqueMember(SearchResultEntry groupEntry, String accountDn) {
MidPointAsserts.assertContainsCaseIgnore("No member "+accountDn+" in group "+getDn(groupEntry),
members, accountDn);
}

public void assertUniqueMember(String groupDn, String accountDn) throws DirectoryException {
SearchResultEntry groupEntry = fetchEntry(groupDn);
assertUniqueMember(groupEntry, accountDn);
}

public void assertNoUniqueMember(String groupDn, String accountDn) throws DirectoryException {
SearchResultEntry groupEntry = fetchEntry(groupDn);
assertNoUniqueMember(groupEntry, accountDn);
}

public void assertNoUniqueMember(SearchResultEntry groupEntry, String accountDn) {
Collection<String> members = getAttributeValues(groupEntry, "uniqueMember");
MidPointAsserts.assertNotContainsCaseIgnore("Member "+accountDn+" in group "+getDn(groupEntry),
members, accountDn);
}

public static void assertAttribute(SearchResultEntry response, String name, String... values) {
List<Attribute> attrs = response.getAttribute(name.toLowerCase());
Expand Down
Expand Up @@ -171,4 +171,15 @@ public static void assertContainsCaseIgnore(String message, Collection<String> a
}
AssertJUnit.fail(message+", expected "+expectedValue+", got "+actualValues);
}

public static void assertNotContainsCaseIgnore(String message, Collection<String> actualValues, String expectedValue) {
if (actualValues == null) {
return;
}
for (String actualValue: actualValues) {
if (StringUtils.equalsIgnoreCase(actualValue, expectedValue)) {
AssertJUnit.fail(message+", expected that value "+expectedValue+" will not be present but it is");
}
}
}
}
Expand Up @@ -228,7 +228,7 @@ public <O extends ObjectType> void executeChanges(LensContext<O> syncContext, Ta
+ accCtx.getResourceShadowDiscriminator());
}
if (focusContext != null) {
updateLinks(focusContext.getObjectNew(), focusContext, accCtx, task,
updateLinks(focusContext, accCtx, task,
subResult);
}

Expand All @@ -254,7 +254,7 @@ public <O extends ObjectType> void executeChanges(LensContext<O> syncContext, Ta
}

if (focusContext != null) {
updateLinks(focusContext.getObjectNew(), focusContext, accCtx, task, subResult);
updateLinks(focusContext, accCtx, task, subResult);
}

executeReconciliationScript(accCtx, syncContext, BeforeAfterType.AFTER, task, subResult);
Expand Down Expand Up @@ -323,17 +323,16 @@ private void recordFatalError(OperationResult subResult, OperationResult result,
/**
* Make sure that the account is linked (or unlinked) as needed.
*/
private <F extends ObjectType> void updateLinks(PrismObject<F> prismObject,
LensFocusContext<F> focusContext, LensProjectionContext projCtx,
private <O extends ObjectType, F extends FocusType> void updateLinks(LensFocusContext<O> focusObjectContext, LensProjectionContext projCtx,
Task task, OperationResult result) throws ObjectNotFoundException, SchemaException {
if (prismObject == null) {
if (focusObjectContext == null) {
return;
}
F objectTypeNew = prismObject.asObjectable();
if (!(objectTypeNew instanceof FocusType)) {
return;
}
FocusType focusTypeNew = (FocusType) objectTypeNew;
Class<O> objectTypeClass = focusObjectContext.getObjectTypeClass();
if (!FocusType.class.isAssignableFrom(objectTypeClass)) {
return;
}
LensFocusContext<F> focusContext = (LensFocusContext<F>) focusObjectContext;

if (projCtx.getResourceShadowDiscriminator() != null && projCtx.getResourceShadowDiscriminator().getOrder() > 0) {
// Don't mess with links for higher-order contexts. The link should be dealt with
Expand All @@ -357,12 +356,13 @@ private <F extends ObjectType> void updateLinks(PrismObject<F> prismObject,
|| projCtx.isDelete()) {
// Link should NOT exist

PrismReference linkRef = focusTypeNew.asPrismObject().findReference(FocusType.F_LINK_REF);
PrismObject<F> objectCurrent = focusContext.getObjectCurrent();
PrismReference linkRef = objectCurrent.findReference(FocusType.F_LINK_REF);
if (linkRef != null) {
for (PrismReferenceValue linkRefVal: linkRef.getValues()) {
if (linkRefVal.getOid().equals(projOid)) {
// Linked, need to unlink
unlinkShadow(focusTypeNew.getOid(), linkRefVal, focusContext, task, result);
unlinkShadow(focusContext.getOid(), linkRefVal, focusObjectContext, task, result);
}
}

Expand All @@ -372,7 +372,7 @@ private <F extends ObjectType> void updateLinks(PrismObject<F> prismObject,
LOGGER.trace("Resource object {} deleted, updating also situation in shadow.", projOid);
// HACK HACK?
try {
updateSituationInShadow(task, SynchronizationSituationType.DELETED, focusContext, projCtx, result);
updateSituationInShadow(task, SynchronizationSituationType.DELETED, focusObjectContext, projCtx, result);
} catch (ObjectNotFoundException e) {
// HACK HACK?
LOGGER.trace("Resource object {} is gone, cannot update situation in shadow (this is probably harmless).", projOid);
Expand All @@ -381,41 +381,44 @@ private <F extends ObjectType> void updateLinks(PrismObject<F> prismObject,
} else {
// This should NOT be UNLINKED. We just do not know the situation here. Reflect that in the shadow.
LOGGER.trace("Resource object {} unlinked from the user, updating also situation in shadow.", projOid);
updateSituationInShadow(task, null, focusContext, projCtx, result);
updateSituationInShadow(task, null, focusObjectContext, projCtx, result);
}
// Not linked, that's OK

} else {
// Link should exist

for (ObjectReferenceType linkRef : focusTypeNew.getLinkRef()) {
if (projOid.equals(linkRef.getOid())) {
// Already linked, nothing to do, only be sure, the situation is set with the good value
LOGGER.trace("Updating situation in already linked shadow.");
updateSituationInShadow(task, SynchronizationSituationType.LINKED, focusContext, projCtx, result);
return;
}
}
PrismObject<F> objectCurrent = focusContext.getObjectCurrent();
if (objectCurrent != null) {
for (ObjectReferenceType linkRef : objectCurrent.asObjectable().getLinkRef()) {
if (projOid.equals(linkRef.getOid())) {
// Already linked, nothing to do, only be sure, the situation is set with the good value
LOGGER.trace("Updating situation in already linked shadow.");
updateSituationInShadow(task, SynchronizationSituationType.LINKED, focusObjectContext, projCtx, result);
return;
}
}
}
// Not linked, need to link
linkShadow(focusTypeNew.getOid(), projOid, focusContext, task, result);
linkShadow(focusContext.getOid(), projOid, focusObjectContext, task, result);
//be sure, that the situation is set correctly
LOGGER.trace("Updating situation after shadow was linked.");
updateSituationInShadow(task, SynchronizationSituationType.LINKED, focusContext, projCtx, result);
updateSituationInShadow(task, SynchronizationSituationType.LINKED, focusObjectContext, projCtx, result);
}
}

private <F extends ObjectType> void linkShadow(String userOid, String accountOid, LensElementContext<F> focusContext, Task task, OperationResult parentResult) throws ObjectNotFoundException,
private <F extends ObjectType> void linkShadow(String userOid, String shadowOid, LensElementContext<F> focusContext, Task task, OperationResult parentResult) throws ObjectNotFoundException,
SchemaException {

Class<F> typeClass = focusContext.getObjectTypeClass();
if (!FocusType.class.isAssignableFrom(typeClass)) {
return;
}

LOGGER.trace("Linking shadow " + accountOid + " to focus " + userOid);
LOGGER.debug("Linking shadow " + shadowOid + " to focus " + userOid);
OperationResult result = parentResult.createSubresult(OPERATION_LINK_ACCOUNT);
PrismReferenceValue linkRef = new PrismReferenceValue();
linkRef.setOid(accountOid);
linkRef.setOid(shadowOid);
linkRef.setTargetType(ShadowType.COMPLEX_TYPE);
Collection<? extends ItemDelta> linkRefDeltas = ReferenceDelta.createModificationAddCollection(
FocusType.F_LINK_REF, getUserDefinition(), linkRef);
Expand All @@ -438,7 +441,7 @@ private PrismObjectDefinition<UserType> getUserDefinition() {
return userDefinition;
}

private <F extends ObjectType> void unlinkShadow(String userOid, PrismReferenceValue accountRef, LensElementContext<F> focusContext,
private <F extends ObjectType> void unlinkShadow(String focusOid, PrismReferenceValue accountRef, LensElementContext<F> focusContext,
Task task, OperationResult parentResult) throws
ObjectNotFoundException, SchemaException {

Expand All @@ -447,19 +450,19 @@ private <F extends ObjectType> void unlinkShadow(String userOid, PrismReferenceV
return;
}

LOGGER.trace("Deleting linkRef " + accountRef + " from focus " + userOid);
LOGGER.debug("Unlinnking shadow " + accountRef.getOid() + " from focus " + focusOid);
OperationResult result = parentResult.createSubresult(OPERATION_UNLINK_ACCOUNT);
Collection<? extends ItemDelta> accountRefDeltas = ReferenceDelta.createModificationDeleteCollection(
FocusType.F_LINK_REF, getUserDefinition(), accountRef.clone());

try {
cacheRepositoryService.modifyObject(typeClass, userOid, accountRefDeltas, result);
cacheRepositoryService.modifyObject(typeClass, focusOid, accountRefDeltas, result);
} catch (ObjectAlreadyExistsException ex) {
result.recordFatalError(ex);
throw new SystemException(ex);
} finally {
result.computeStatus();
ObjectDelta<F> userDelta = ObjectDelta.createModifyDelta(userOid, accountRefDeltas, typeClass, prismContext);
ObjectDelta<F> userDelta = ObjectDelta.createModifyDelta(focusOid, accountRefDeltas, typeClass, prismContext);
LensObjectDeltaOperation<F> userDeltaOp = new LensObjectDeltaOperation<F>(userDelta);
userDeltaOp.setExecutionResult(result);
focusContext.addToExecutedDeltas(userDeltaOp);
Expand Down
Expand Up @@ -58,6 +58,8 @@
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.TunnelException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_2a.ResourceObjectAssociationDirectionType;
import com.evolveum.midpoint.xml.ns._public.common.common_2a.ResourceObjectAssociationType;
import com.evolveum.midpoint.xml.ns._public.common.common_2a.ResourceType;
Expand All @@ -76,6 +78,8 @@
@Component
class EntitlementConverter {

private static final Trace LOGGER = TraceManager.getTrace(EntitlementConverter.class);

@Autowired(required=true)
private PrismContext prismContext;

Expand Down Expand Up @@ -307,13 +311,21 @@ public <T> void collectEntitlementsAsObjectOperationDelete(ConnectorInstance con
Collection<RefinedAssociationDefinition> entitlementAssociationDefs = objectClassDefinition.getEntitlementAssociations();
if (entitlementAssociationDefs == null || entitlementAssociationDefs.isEmpty()) {
// Nothing to do
LOGGER.trace("No associations in deleted shadow");
return;
}
RefinedResourceSchema refinedSchema = RefinedResourceSchema.getRefinedSchema(resourceType);
ResourceAttributeContainer attributesContainer = ShadowUtil.getAttributesContainer(shadow);
for (RefinedAssociationDefinition assocDefType: objectClassDefinition.getEntitlementAssociations()) {
if (assocDefType.getResourceObjectAssociationType().getDirection() != ResourceObjectAssociationDirectionType.OBJECT_TO_SUBJECT) {
// We can ignore these. They will die together with the object. No need to explicitly delete them.
LOGGER.trace("Ignoring object-to-subject association in deleted shadow");
continue;
}
if (assocDefType.getResourceObjectAssociationType().isExplicitReferentialIntegrity() != null
&& !assocDefType.getResourceObjectAssociationType().isExplicitReferentialIntegrity()) {
// Referential integrity not required for this one
LOGGER.trace("Ignoring association in deleted shadow because it has explicity referential integrity turned off");
continue;
}
QName associationName = assocDefType.getName();
Expand Down Expand Up @@ -347,6 +359,7 @@ public <T> void collectEntitlementsAsObjectOperationDelete(ConnectorInstance con
}

// ObjectFilter filter = EqualsFilter.createEqual(new ItemPath(ShadowType.F_ATTRIBUTES), assocAttrDef, valueAttr.getValue());
// ObjectFilter filter = InFilter.createIn(new ItemPath(ShadowType.F_ATTRIBUTES, assocAttrDef.getName()), assocAttrDef, valueAttr.getValue());
ObjectFilter filter = EqualsFilter.createEqual(new ItemPath(ShadowType.F_ATTRIBUTES, assocAttrDef.getName()), assocAttrDef, valueAttr.getValue());
ObjectQuery query = ObjectQuery.createObjectQuery(filter);
// new ObjectQuery();
Expand Down Expand Up @@ -381,11 +394,13 @@ public boolean handle(PrismObject<ShadowType> entitlementShadow) {
}

attributeDelta.addValuesToDelete(valueAttr.getClonedValues());
LOGGER.trace("Association in deleted shadow delta: {}", attributeDelta);

return true;
}
};
try {
LOGGER.trace("Searching for associations in deleted shadow, query: {}", filter);
connector.search(entitlementOcDef, query, handler, attributesToReturn, parentResult);
} catch (TunnelException e) {
throw (SchemaException)e.getCause();
Expand Down
Expand Up @@ -30,6 +30,7 @@
import com.evolveum.midpoint.prism.PrismValue;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.query.EqualsFilter;
import com.evolveum.midpoint.prism.query.InFilter;
import com.evolveum.midpoint.prism.query.ObjectFilter;
import com.evolveum.midpoint.prism.query.SubstringFilter;
import com.evolveum.midpoint.prism.query.ValueFilter;
Expand Down Expand Up @@ -79,6 +80,22 @@ public <T> Filter interpret(ObjectFilter objectFilter, IcfNameMapper icfNameMapp
return FilterBuilder.containsAllValues(attr);
}
}

} else if (objectFilter instanceof InFilter) {
InFilter<T> in = (InFilter<T>) objectFilter;

Collection<Object> convertedValues = new ArrayList<Object>();
for (PrismValue value : in.getValues()) {
Object converted = UcfUtil.convertValueToIcf(value, null, propName);
convertedValues.add(converted);
}

if (convertedValues.isEmpty()) {
throw new IllegalArgumentException("In filter with a null value makes no sense");
} else {
Attribute attr = AttributeBuilder.build(icfName, convertedValues);
return FilterBuilder.equalTo(attr);
}

} else if (objectFilter instanceof SubstringFilter) {
SubstringFilter substring = (SubstringFilter) objectFilter;
Expand Down
Expand Up @@ -15,6 +15,8 @@
*/
package com.evolveum.midpoint.provisioning.test.impl;

import java.io.File;

import javax.xml.namespace.QName;

import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -41,6 +43,7 @@
public abstract class AbstractOpenDJTest extends AbstractIntegrationTest {

protected static final String TEST_DIR_NAME = "src/test/resources/impl/opendj";
protected static final File TEST_DIR = new File(TEST_DIR_NAME);

protected static final String RESOURCE_OPENDJ_FILENAME = ProvisioningTestUtil.COMMON_TEST_DIR_FILENAME + "resource-opendj.xml";
protected static final String RESOURCE_OPENDJ_INITIALIZED_FILENAME = ProvisioningTestUtil.COMMON_TEST_DIR_FILENAME + "resource-opendj-initialized.xml";
Expand Down Expand Up @@ -88,6 +91,14 @@ public abstract class AbstractOpenDJTest extends AbstractIntegrationTest {
protected static final String ACCOUNT_NO_SN_FILENAME = TEST_DIR_NAME + "/account-opendj-no-sn.xml";
protected static final String ACCOUNT_NO_SN_OID = "c0c010c0-d34d-beef-f33d-113222123444";

protected static final File ACCOUNT_MORGAN_FILE = new File(TEST_DIR, "account-morgan.xml");
protected static final String ACCOUNT_MORGAN_OID = "8dfcf05e-c571-11e3-abbd-001e8c717e5b";
protected static final String ACCOUNT_MORGAN_DN = "uid=morgan,ou=People,dc=example,dc=com";

protected static final File GROUP_SWASHBUCKLERS_FILE = new File(TEST_DIR, "group-swashbucklers.xml");
protected static final String GROUP_SWASHBUCKLERS_OID = "3d96846e-c570-11e3-a80f-001e8c717e5b";
protected static final String GROUP_SWASHBUCKLERS_DN = "cn=swashbucklers,ou=Groups,dc=example,dc=com";

protected static final String NON_EXISTENT_OID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";

protected static final String RESOURCE_NS = "http://midpoint.evolveum.com/xml/ns/public/resource/instance/ef2bc95b-76e0-59e2-86d6-3d4f02d3ffff";
Expand Down

0 comments on commit d1787c4

Please sign in to comment.