Skip to content

Commit

Permalink
Add support for object references (BASE-90) (#167)
Browse files Browse the repository at this point in the history
  • Loading branch information
mederly authored Sep 24, 2024
1 parent ac2b60d commit 81fd503
Show file tree
Hide file tree
Showing 15 changed files with 666 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,8 @@ public Object deserialize(final ObjectDecoder decoder) {
builder.setFlags(flags);
builder.setNativeName(decoder.readStringField("nativeName", null));
builder.setSubtype(decoder.readStringField("subtype", null));
builder.setReferencedObjectClassName(decoder.readStringField("referencedObjectClassName", null));
builder.setRoleInReference(decoder.readStringField("roleInReference", null));
return builder.build();
}

Expand All @@ -392,6 +394,8 @@ public void serialize(final Object object, final ObjectEncoder encoder) {
}
encoder.writeStringField("nativeName", val.getNativeName());
encoder.writeStringField("subtype", val.getSubtype());
encoder.writeStringField("referencedObjectClassName", val.getReferencedObjectClassName());
encoder.writeStringField("roleInReference", val.getRoleInReference());
}
});

Expand All @@ -417,6 +421,41 @@ public void serialize(final Object object, final ObjectEncoder encoder) {
}
});

HANDLERS.add(new AbstractObjectSerializationHandler(ConnectorObjectIdentification.class,
"ConnectorObjectIdentification") {

@SuppressWarnings("unchecked")
@Override
public Object deserialize(ObjectDecoder decoder) {
return new ConnectorObjectIdentification(
(ObjectClass) decoder.readObjectField("ObjectClass", ObjectClass.class, null),
(Set<? extends Attribute>) decoder.readObjectField("Attributes", Set.class, null));
}

@Override
public void serialize(final Object object, final ObjectEncoder encoder) {
final ConnectorObjectIdentification val = (ConnectorObjectIdentification) object;
encoder.writeObjectField("ObjectClass", val.getObjectClass(), true);
encoder.writeObjectField("Attributes", val.getAttributes(), true);
}
});

HANDLERS.add(new AbstractObjectSerializationHandler(ConnectorObjectReference.class,
"ConnectorObjectReference") {

@Override
public Object deserialize(ObjectDecoder decoder) {
return new ConnectorObjectReference(
(BaseConnectorObject) decoder.readObjectField("Value", BaseConnectorObject.class, null));
}

@Override
public void serialize(final Object object, final ObjectEncoder encoder) {
final ConnectorObjectReference val = (ConnectorObjectReference) object;
encoder.writeObjectField("Value", val.getValue(), true);
}
});

HANDLERS.add(new AbstractObjectSerializationHandler(Name.class, "Name") {

@Override
Expand Down Expand Up @@ -455,12 +494,13 @@ public Object deserialize(final ObjectDecoder decoder) {
final String type = decoder.readStringField("type", null);
final boolean container = decoder.readBooleanField("container", false);
final boolean auxiliary = decoder.readBooleanField("auxiliary", false);
final boolean embedded = decoder.readBooleanField("embedded", false);

@SuppressWarnings("unchecked")
final Set<AttributeInfo> attrInfo =
(Set) decoder.readObjectField("AttributeInfos", Set.class, null);

return new ObjectClassInfo(type, attrInfo, container, auxiliary);
return new ObjectClassInfo(type, attrInfo, container, auxiliary, embedded);
}

@Override
Expand All @@ -470,6 +510,7 @@ public void serialize(final Object object, final ObjectEncoder encoder) {
encoder.writeStringField("type", val.getType());
encoder.writeBooleanField("container", val.isContainer());
encoder.writeBooleanField("auxiliary", val.isAuxiliary());
encoder.writeBooleanField("embedded", val.isEmbedded());
encoder.writeObjectField("AttributeInfos", val.getAttributeInfo(), true);
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,6 @@
*/
package org.identityconnectors.framework.impl.api;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
Expand Down Expand Up @@ -61,19 +55,28 @@
import org.identityconnectors.framework.common.objects.Attribute;
import org.identityconnectors.framework.common.objects.AttributeBuilder;
import org.identityconnectors.framework.common.objects.ConnectorObject;
import org.identityconnectors.framework.common.objects.ConnectorObjectReference;
import org.identityconnectors.framework.common.objects.ConnectorObjectIdentification;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.identityconnectors.framework.common.objects.OperationOptions;
import org.identityconnectors.framework.common.objects.OperationOptionsBuilder;
import org.identityconnectors.framework.common.objects.ScriptContextBuilder;
import org.identityconnectors.framework.common.objects.SyncDelta;
import org.identityconnectors.framework.common.objects.SyncToken;
import org.identityconnectors.framework.common.objects.Schema;
import org.identityconnectors.framework.common.objects.ObjectClassInfo;
import org.identityconnectors.framework.common.objects.AttributeInfo;
import org.identityconnectors.framework.common.objects.Name;
import org.identityconnectors.framework.impl.api.local.ConnectorPoolManager;
import org.identityconnectors.testconnector.TstConnector;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;

import static org.junit.jupiter.api.Assertions.*;

public abstract class ConnectorInfoManagerTestBase {

private static ConnectorInfo findConnectorInfo(ConnectorInfoManager manager, String version, String connectorName) {
Expand Down Expand Up @@ -348,6 +351,86 @@ public void testSearchStress() throws Exception {
System.out.println("Test took: " + (end - start) / 1000);
}

/** Checks the schema (mainly regarding reference attributes). */
@Test
public void testSchema() throws Exception {
ConnectorInfoManager manager = getConnectorInfoManager();
ConnectorInfo info = findConnectorInfo(manager,
"1.0.0.0",
"org.identityconnectors.testconnector.TstConnector");

ConnectorFacade facade = ConnectorFacadeFactory.getInstance()
.newInstance(info.createDefaultAPIConfiguration());
Schema schema = facade.schema();

assertEquals(5, schema.getObjectClassInfo().size());

ObjectClassInfo userObjectClass = schema.findObjectClassInfo(TstConnector.USER_CLASS_NAME);
assertNotNull(userObjectClass);
userObjectClass.getAttributeInfo().stream()
.filter(attr -> attr.getName().equals(TstConnector.MEMBER_OF_ATTR_NAME))
.findFirst()
.ifPresentOrElse(attr -> {
assertEquals(TstConnector.GROUP_CLASS_NAME, attr.getReferencedObjectClassName());
assertEquals(TstConnector.GROUP_MEMBERSHIP_REFERENCE_TYPE_NAME, attr.getSubtype());
assertEquals(AttributeInfo.RoleInReference.SUBJECT.toString(), attr.getRoleInReference());
assertTrue(attr.isMultiValued());
}, () -> {
fail("Attribute " + TstConnector.MEMBER_OF_ATTR_NAME + " not found");
});

ObjectClassInfo accessObjectClass = schema.findObjectClassInfo(TstConnector.ACCESS_CLASS_NAME);
assertNotNull(accessObjectClass);
assertTrue(accessObjectClass.isEmbedded());
}

/** Checks that the connector can return objects with references. */
@Test
public void testSearchWithReferences() throws Exception {
ConnectorInfoManager manager = getConnectorInfoManager();
ConnectorInfo info = findConnectorInfo(manager,
"1.0.0.0",
"org.identityconnectors.testconnector.TstConnector");

ConnectorFacade facade = ConnectorFacadeFactory.getInstance()
.newInstance(info.createDefaultAPIConfiguration());

final List<ConnectorObject> results = new ArrayList<>();

facade.search(TstConnector.userObjectClass(), null, (final ConnectorObject obj) -> {
results.add(obj);
return true;
}, null);

assertEquals(2, results.size());
ConnectorObject user100 = results.get(0);
assertEquals(TstConnector.USER_100_UID, user100.getUid().getUidValue());
assertEquals(TstConnector.USER_100_NAME, user100.getName().getNameValue());

List<?> user100Groups = user100.getAttributeByName(TstConnector.MEMBER_OF_ATTR_NAME).getValue();

assertEquals(2, user100Groups.size());
ConnectorObjectReference firstGroupRef = (ConnectorObjectReference) user100Groups.get(0);
assertTrue(firstGroupRef.hasObject());
ConnectorObject firstGroup = (ConnectorObject) firstGroupRef.getValue();
assertEquals(TstConnector.GROUP_1_UID, firstGroup.getUid().getUidValue());
assertEquals(TstConnector.GROUP_1_NAME, firstGroup.getName().getNameValue());
assertEquals(2, firstGroup.getAttributeByName(TstConnector.MEMBERS_ATTR_NAME).getValue().size());

ConnectorObjectReference secondGroupRef = (ConnectorObjectReference) user100Groups.get(1);
assertTrue(secondGroupRef.hasObject());
ConnectorObject secondGroup = (ConnectorObject) secondGroupRef.getValue();
assertEquals(TstConnector.GROUP_2_UID, secondGroup.getUid().getUidValue());
assertEquals(TstConnector.GROUP_2_NAME, secondGroup.getName().getNameValue());
List<?> memberRefs = secondGroup.getAttributeByName(TstConnector.MEMBERS_ATTR_NAME).getValue();
assertEquals(1, memberRefs.size());
ConnectorObjectReference firstMemberRef = (ConnectorObjectReference) memberRefs.get(0);
assertFalse(firstMemberRef.hasObject());
ConnectorObjectIdentification firstMemberIds = (ConnectorObjectIdentification) firstMemberRef.getValue();
assertEquals(TstConnector.USER_100_NAME, firstMemberIds.getAttributeByName(Name.NAME).getValue().get(0));
assertEquals(1, firstMemberIds.getAttributes().size());
}

@RepeatedTest(100)
@Test
public void testSchemaStress(TestInfo testInfo) throws Exception {
Expand Down Expand Up @@ -578,7 +661,7 @@ public void testTimeout() throws Exception {
}
}

protected final File getTestBundlesDir() throws URISyntaxException {
final File getTestBundlesDir() throws URISyntaxException {
URL testOutputDirectory = ConnectorInfoManagerTestBase.class.getResource("/");
File testBundlesDir = new File(testOutputDirectory.toURI());
if (!testBundlesDir.isDirectory()) {
Expand All @@ -587,14 +670,22 @@ protected final File getTestBundlesDir() throws URISyntaxException {
return testBundlesDir;
}

protected final List<URL> getTestBundles() throws Exception {
File testBundlesDir = getTestBundlesDir();
// Originally, this method used getTestBundlesDir. We stopped doing that in order to allow tests to be run directly from IDE.
List<URL> getTestBundles() {
List<URL> rv = new ArrayList<>();
rv.add(IOUtil.makeURL(testBundlesDir, "testbundlev1.jar"));
rv.add(IOUtil.makeURL(testBundlesDir, "testbundlev2.jar"));
rv.add(getTestBundleUrl("testbundlev1.jar"));
rv.add(getTestBundleUrl("testbundlev2.jar"));
return rv;
}

private URL getTestBundleUrl(String name) {
URL url = ConnectorInfoManagerTestBase.class.getResource("/" + name);
if (url == null) {
throw new IllegalStateException("Bundle '" + name + "' could not be found");
}
return url;
}

/**
* To be overridden by subclasses to get different ConnectorInfoManagers
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,7 @@
import org.identityconnectors.common.security.GuardedString;
import org.identityconnectors.framework.api.operations.*;
import org.identityconnectors.framework.common.exceptions.ConnectorException;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.identityconnectors.framework.common.objects.QualifiedUid;
import org.identityconnectors.framework.common.objects.SortKey;
import org.identityconnectors.framework.common.objects.Uid;
import org.identityconnectors.framework.common.objects.*;
import org.identityconnectors.framework.spi.Connector;
import org.identityconnectors.framework.spi.operations.*;

Expand Down Expand Up @@ -240,6 +237,7 @@ public static boolean isSupportedConfigurationType(Class<?> clazz) {
ATTR_SUPPORTED_TYPES.add(GuardedString.class);
ATTR_SUPPORTED_TYPES.add(Map.class);
ATTR_SUPPORTED_TYPES.add(ZonedDateTime.class);
ATTR_SUPPORTED_TYPES.add(ConnectorObjectReference.class);
}

public static Set<Class<? extends Object>> getAllSupportedAttributeTypes() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public final class AttributeBuilder {

List<Object> value;

AttributeValueCompleteness attributeValueCompleteness = AttributeValueCompleteness.COMPLETE;
AttributeValueCompleteness attributeValueCompleteness = AttributeValueCompleteness.COMPLETE;

/**
* Creates a attribute with the specified name and a {@code null} value.
Expand Down
Loading

0 comments on commit 81fd503

Please sign in to comment.