Implementing the Operation Interfaces

Important

Connectors continue to be released outside the IDM release. For the latest documentation, refer to the ICF documentation.

The SPI provides several operations. The subset of operations that you implement will depend on the target resource to which you are connecting. Each operation interface defines an action that the connector can perform on the target resource.

The following sections describe the operation interfaces that are provided by the SPI, and provide examples of how they can be implemented in your connector. The sections include the API- and SPI-level rules for each operation.

Authenticate Operation

The authenticate operation authenticates an object on the target system, based on two parameters, usually a unique identifier (username) and a password. If possible, your connector should try to authenticate these credentials natively.

If authentication fails, the connector should throw a runtime exception. The exception must be an IllegalArgumentException or, if a native exception is available and is of type RuntimeException, that native runtime exception. If the native exception is not a RuntimeException, it should be wrapped in a RuntimeException, and then thrown.

The exception should provide as much detail as possible for logging problems and failed authentication attempts. Several exceptions are provided in the exceptions package, for this purpose. For example, one of the most common authentication exceptions is the InvalidPasswordException.

For more information about the common exceptions provided in the OpenICF framework, see "Common Exceptions".

Using the ICF Authenticate Operation

This section shows how your application can use the framework's authentication operation, and how to write a unit test for this operation, when you are developing your connector.

The authentication operation throws a RuntimeException if the credentials do not pass authentication, otherwise returns the UID.

Sample Unit Test for the Authentication Operation (Java)
@Test
public void authenticateTest() {
    logger.info("Running Authentication Test");
    final ConnectorFacade facade = createConnectorFacade(BasicConnector.class, null);
    final OperationOptionsBuilder builder = new OperationOptionsBuilder();
    Uid uid =
        facade.authenticate(ObjectClass.ACCOUNT, "username", new GuardedString("Passw0rd"
        .toCharArray()), builder.build());
    Assert.assertEquals(uid.getUidValue(), "username");
}

Implementing the Authenticate Operation in Your Connector

To implement the authenticate operation in your connector, add the AuthenticateOp interface to your connector class, for example:

@ConnectorClass(
    displayNameKey = "Sample.connector.display",
    configurationClass = SampleConfiguration.class)
public class SampleConnector implements Connector, AuthenticateOp...
    

For more information, see the AuthenticateOp JavaDoc.

The SPI provides the following detailed exceptions:

  • UnknownUidException - the UID does not exist on the resource

    (org.identityconnectors.framework.common.exceptions.UnknownUidException)

  • ConnectorSecurityException - base exception for all security-related exceptions

    (org.identityconnectors.framework.common.exceptions.ConnectorSecurityException)

  • InvalidCredentialException - generic invalid credential exception that should be used if the specific error cannot be obtained

    (org.identityconnectors.framework.common.exceptions.UnknownUidException)

  • InvalidPasswordException - the password provided is incorrect

    (org.identityconnectors.framework.common.exceptions.InvalidPasswordException)

  • PasswordExpiredException - the password is correct, but has expired

    (org.identityconnectors.framework.common.exceptions.PasswordExpiredException)

  • PermissionDeniedException - the user can be identified but does not have permission to authenticate

    (org.identityconnectors.framework.common.exceptions.PermissionDeniedException)

Implementation of the Authentication Operation, at the SPI Level
public Uid authenticate(final ObjectClass objectClass, final String userName,
        final GuardedString password, final OperationOptions options) {
    if (ObjectClass.ACCOUNT.equals(objectClass)) {
        return new Uid(userName);
    } else {
        logger.warn("Authenticate of type {0} is not supported", configuration
                .getConnectorMessages().format(objectClass.getDisplayNameKey(),
                        objectClass.getObjectClassValue()));
        throw new UnsupportedOperationException("Authenticate of type"
                + objectClass.getObjectClassValue() + " is not supported");
    }
}

Create Operation

The create operation interface enables the connector to create objects on the target system. The operation includes one method (create()). The method takes an ObjectClass, and any provided attributes, and creates the object and its UID. The connector must return the UID so that the caller can refer to the created object.

The connector should make a best effort to create the object, and should throw an informative RuntimeException, indicating to the caller why the operation could not be completed. Defaults can be used for any required attributes, as long as the defaults are documented.

The UID is never passed in with the attribute set for this method. If the resource supports a mutable UID, you can create a resource-specific attribute for the ID, such as unix_uid.

If the create operation is only partially successful, the connector should attempt to roll back the partial change. If the target system does not allow this, the connector should report the partial success of the create operation and throw a RetryableException. For example:

public static RetryableException wrap(final String message, final Uid uid) {
    return new RetryableException(message, new AlreadyExistsException().initUid(Assertions
    .nullChecked(uid, "Uid")));
}

Using the ICF Create Operation

The following exceptions are thrown by the Create API operation:

  • IllegalArgumentException - if ObjectClass} is missing, or if elements of the set produce duplicate values of Attribute#getName()

  • NullPointerException - if the createAttributes parameter is null

  • RuntimeException - if the Connector SPI throws a native exception

Consumption of the Create Operation, at the API Level
@Test
public void createTest() {
    logger.info("Running Create Test");
    final ConnectorFacade facade = createConnectorFacade(BasicConnector.class, null);
    final OperationOptionsBuilder builder = new OperationOptionsBuilder();
    Set<Attribute> createAttributes = new HashSet<Attribute>();
    createAttributes.add(new Name("Foo"));
    createAttributes.add(AttributeBuilder.buildPassword("Password".toCharArray()));
    createAttributes.add(AttributeBuilder.buildEnabled(true));
    Uid uid = facade.create(ObjectClass.ACCOUNT, createAttributes, builder.build());
    Assert.assertEquals(uid.getUidValue(), "foo");
}

Implementing the Create Operation in Your Connector

The SPI provides the following detailed exceptions:

  • UnsupportedOperationException - the create operation is not supported for the specified object class

  • InvalidAttributeValueException - a required attribute is missing, an attribute is present that cannot be created, or a provided attribute has an invalid value

  • AlreadyExistsException - an object with the specified Name already exits on the target system

  • PermissionDeniedException - the target resource will not allow the connector to perform the specified operation

  • ConnectorIOException, ConnectionBrokenException, ConnectionFailedException - a problem as occurred with the connection

  • RuntimeException - thrown if anything else goes wrong. You should try to throw a native exception in this case.

Implementation of the Create Operation, at the SPI Level
public Uid create(final ObjectClass objectClass, final Set<Attribute> createAttributes,
        final OperationOptions options) {
    if (ObjectClass.ACCOUNT.equals(objectClass) || ObjectClass.GROUP.equals(objectClass)) {
        Name name = AttributeUtil.getNameFromAttributes(createAttributes);
        if (name != null) {
            // do real create here
            return new Uid(AttributeUtil.getStringValue(name).toLowerCase());
        } else {
            throw new InvalidAttributeValueException("Name attribute is required");
        }
    } else {
        logger.warn("Delete of type {0} is not supported", configuration.getConnectorMessages()
                .format(objectClass.getDisplayNameKey(), objectClass.getObjectClassValue()));
        throw new UnsupportedOperationException("Delete of type"
                + objectClass.getObjectClassValue() + " is not supported");
    }
}

Delete Operation

The delete operation interface enables the connector to delete an object on the target system. The operation includes one method (delete()). The method takes an ObjectClass, a Uid, and any operation options.

The connector should call the native delete methods to remove the object, specified by its unique ID.

Using the ICF Delete Operation

The following exceptions are thrown by the Delete API operation:

  • UnknownUidException - the UID does not exist on the resource

Consumption of the Delete Operation, at the API Level
@Test
public void deleteTest() {
    logger.info("Running Delete Test");
    final ConnectorFacade facade = createConnectorFacade(BasicConnector.class, null);
    final OperationOptionsBuilder builder = new OperationOptionsBuilder();
    facade.delete(ObjectClass.ACCOUNT, new Uid("username"), builder.build());
}

Implementing the Delete Operation in Your Connector

Implementation of the Delete Operation, at the SPI Level
public void delete(final ObjectClass objectClass, final Uid uid, final OperationOptions options) {
    if (ObjectClass.ACCOUNT.equals(objectClass) || ObjectClass.GROUP.equals(objectClass)) {
        // do real delete here
    } else {
        logger.warn("Delete of type {0} is not supported", configuration.getConnectorMessages()
                .format(objectClass.getDisplayNameKey(), objectClass.getObjectClassValue()));
        throw new UnsupportedOperationException("Delete of type"
                + objectClass.getObjectClassValue() + " is not supported");
    }
}

Resolve Username Operation

The resolve username operation enables the connector to resolve an object to its UID, based on its username. This operation is similar to the simple authentication operation. However, the resolve username operation does not include a password parameter, and does not attempt to authenticate the credentials. Instead, it returns the UID that corresponds to the supplied username.

The implementation must, however, validate the username (that is, the connector must throw an exception if the username does not correspond to an existing object). If the username validation fails, the the connector should throw a runtime exception, either an IllegalArgumentException or, if a native exception is available and is of type RuntimeException, simply throw that exception. If the native exception is not a RuntimeException, it should be wrapped in a RuntimeException, and then thrown.

The exception should provide as much detail as possible for logging problems and failed attempts. Several exceptions are provided in the exceptions package, for this purpose. For example, one of the most common exceptions is the UnknownUidException.

Using the ICF Resolve Username Operation

The operation throws a RuntimeException if the username validation fails, otherwise returns the UID.

Consumption of the ResolveUsername operation, at the API Level
@Test
public void resolveUsernameTest() {
    logger.info("Running ResolveUsername Test");
    final ConnectorFacade facade = createConnectorFacade(BasicConnector.class, null);
    final OperationOptionsBuilder builder = new OperationOptionsBuilder();
    Uid uid = facade.resolveUsername(ObjectClass.ACCOUNT, "username", builder.build());
    Assert.assertEquals(uid.getUidValue(), "username");
}

Implementing the Resolve Username Operation in Your Connector

The SPI provides the following detailed exceptions:

  • UnknownUidException - the UID does not exist on the resource

Implementation of the ResolveUsername Operation, at the SPI Level
public Uid resolveUsername(final ObjectClass objectClass, final String userName,
        final OperationOptions options) {
    if (ObjectClass.ACCOUNT.equals(objectClass)) {
        return new Uid(userName);
    } else {
        logger.warn("ResolveUsername of type {0} is not supported", configuration
                .getConnectorMessages().format(objectClass.getDisplayNameKey(),
                        objectClass.getObjectClassValue()));
        throw new UnsupportedOperationException("ResolveUsername of type"
                + objectClass.getObjectClassValue() + " is not supported");
    }
}

Schema Operation

The Schema Operation interface enables the connector to describe the types of objects that it can handle on the target system, and the operations and options that the connector supports for each object type.

The operation has one method, schema(), which returns the types of objects on the target system that the connector supports. The method should return the object class name, its description, and a set of attribute definitions.

The implementation of this operation includes a mapping between the native object class and the corresponding connector object. The special Uid attribute should not be returned, because it is not a true attribute of the object, but a reference to it. For more information about special attributes in ICF, see "ICF Special Attributes".

If your resource object class has a writable unique ID attribute that is different to its Name, your schema should contain a resource-specific attribute that represents this unique ID. For example, a Unix account object might contain a unix_uid.

Using the ICF Schema Operation

Consumption of the Schema Operation, at the API Level
@Test
public void schemaTest() {
    logger.info("Running Schema Test");
    final ConnectorFacade facade = createConnectorFacade(BasicConnector.class, null);
    Schema schema = facade.schema();
    Assert.assertNotNull(schema.findObjectClassInfo(ObjectClass.ACCOUNT_NAME));
}

Implementing the Schema Operation in Your Connector

Implementation of the SchemaOp operation, at the SPI Level
public Schema schema() {
    if (null == schema) {
        final SchemaBuilder builder = new SchemaBuilder(BasicConnector.class);
        // Account
        ObjectClassInfoBuilder accountInfoBuilder = new ObjectClassInfoBuilder();
        accountInfoBuilder.addAttributeInfo(Name.INFO);
        accountInfoBuilder.addAttributeInfo(OperationalAttributeInfos.PASSWORD);
        accountInfoBuilder.addAttributeInfo(PredefinedAttributeInfos.GROUPS);
        accountInfoBuilder.addAttributeInfo(AttributeInfoBuilder.build("firstName"));
        accountInfoBuilder.addAttributeInfo(AttributeInfoBuilder.define("lastName")
                .setRequired(true).build());
        builder.defineObjectClass(accountInfoBuilder.build());

        // Group
        ObjectClassInfoBuilder groupInfoBuilder = new ObjectClassInfoBuilder();
        groupInfoBuilder.setType(ObjectClass.GROUP_NAME);
        groupInfoBuilder.addAttributeInfo(Name.INFO);
        groupInfoBuilder.addAttributeInfo(PredefinedAttributeInfos.DESCRIPTION);
        groupInfoBuilder.addAttributeInfo(AttributeInfoBuilder.define("members").setCreatable(
                false).setUpdateable(false).setMultiValued(true).build());

        // Only the CRUD operations
        builder.defineObjectClass(groupInfoBuilder.build(), CreateOp.class, SearchOp.class,
                UpdateOp.class, DeleteOp.class);

        // Operation Options
        builder.defineOperationOption(OperationOptionInfoBuilder.buildAttributesToGet(),
                SearchOp.class);

        // Support paged Search
        builder.defineOperationOption(OperationOptionInfoBuilder.buildPageSize(),
                SearchOp.class);
        builder.defineOperationOption(OperationOptionInfoBuilder.buildPagedResultsCookie(),
                SearchOp.class);

        // Support to execute operation with provided credentials
        builder.defineOperationOption(OperationOptionInfoBuilder.buildRunWithUser());
        builder.defineOperationOption(OperationOptionInfoBuilder.buildRunWithPassword());

        schema = builder.build();
    }
    return schema;
}

Script On Connector Operation

The script on connector operation runs a script in the environment of the connector. This is different to the script on resource operation, which runs a script on the target resource that the connector manages.

The corresponding API operation (scriptOnConnectorApiOp) provides a minimum contract to which the connector must adhere. (See the javadoc for more information). If you do not implement the scriptOnConnector interface in your connector, the framework provides a default implementation. If you intend your connector to provide more to the script than what is required by this minimum contract, you must implement the scriptOnConnectorOp interface.

Using the ICF Script on Connector Operation

The API operation allows an application to run a script in the context of any connector.

This operation runs the script in the same JVM or .Net Runtime as the connector. That is, if you are using a local framework, the script runs in your JVM. If you are connected to a remote framework, the script runs in the remote JVM or .Net Runtime.

Consumption of the ScriptOnConnector operation, at the API Level
@Test
public void runScriptOnConnectorTest() {
    logger.info("Running RunScriptOnConnector Test");
    final ConnectorFacade facade = createConnectorFacade(BasicConnector.class, null);
    final OperationOptionsBuilder builder = new OperationOptionsBuilder();
    builder.setRunAsUser("admin");
    builder.setRunWithPassword(new GuardedString("Passw0rd".toCharArray()));

    final ScriptContextBuilder scriptBuilder =
            new ScriptContextBuilder("Groovy", "return argument");
    scriptBuilder.addScriptArgument("argument", "value");

    Object result = facade.runScriptOnConnector(scriptBuilder.build(), builder.build());
    Assert.assertEquals(result, "value");
}    

Implementing the Script on Connector Operation in Your Connector

The scriptOnConnector SPI operation takes the following parameters:

  • request - the script and the arguments to be run

  • options - additional options that control how the script is run

The operation returns the result of the script. The return type must be a type that the framework supports for serialization. See the ObjectSerializerFactory javadoc for a list of supported return types.

Implementation of the ScriptOnConnector operation, at the SPI Level
public Object runScriptOnConnector(ScriptContext request, OperationOptions options) {
    final ScriptExecutorFactory factory =
            ScriptExecutorFactory.newInstance(request.getScriptLanguage());
    final ScriptExecutor executor =
            factory.newScriptExecutor(getClass().getClassLoader(), request.getScriptText(),
                    true);

    if (StringUtil.isNotBlank(options.getRunAsUser())) {
        String password = SecurityUtil.decrypt(options.getRunWithPassword());
        // Use these to execute the script with these credentials
    }
    try {
        return executor.execute(request.getScriptArguments());
    } catch (Throwable e) {
        logger.warn(e, "Failed to execute Script");
        throw ConnectorException.wrap(e);
    }
}    

Script On Resource Operation

The script on resource operation runs a script directly on the target resource (unlike the "Script On Connector Operation", which runs a script in the context of a specific connector.)

Implement this interface if your connector intends to support the ScriptOnResourceApiOp API operation. If your connector implements this interface, you must document the script languages that the connector supports, as well as any supported OperationOptions.

Using the ICF Script on Resource Operation

The contract at the API level is intentionally very loose. Each connector decides what script languages it supports, what running a script on a target resource actually means, and what script options (if any) the connector supports.

Consumption of the ScriptOnResource operation, at the API Level
@Test
public void runScriptOnResourceTest() {
    logger.info("Running RunScriptOnResource Test");
    final ConnectorFacade facade = createConnectorFacade(BasicConnector.class, null);
    final OperationOptionsBuilder builder = new OperationOptionsBuilder();
    builder.setRunAsUser("admin");
    builder.setRunWithPassword(new GuardedString("Passw0rd".toCharArray()));

    final ScriptContextBuilder scriptBuilder = new ScriptContextBuilder("bash", "whoami");

    Object result = facade.runScriptOnResource(scriptBuilder.build(), builder.build());
    Assert.assertEquals(result, "admin");
}    

Implementing the Script on Resource Operation in Your Connector

The scriptOnResource SPI operation takes the following parameters:

  • request - the script and the arguments to be run

  • options - additional options that control how the script is run

The operation returns the result of the script. The return type must be a type that the framework supports for serialization. See the ObjectSerializerFactory javadoc for a list of supported return types.

Implementation of the ScriptOnResource operation, at the SPI Level
public Object runScriptOnResource(ScriptContext request, OperationOptions options) {
    try {
        // Execute the script on remote resource
        if (StringUtil.isNotBlank(options.getRunAsUser())) {
            String password = SecurityUtil.decrypt(options.getRunWithPassword());
            // Use these to execute the script with these credentials
            return options.getRunAsUser();
        }
        throw new UnknownHostException("Failed to connect to remote SSH");
    } catch (Throwable e) {
        logger.warn(e, "Failed to execute Script");
        throw ConnectorException.wrap(e);
    }
}    

Sync Operation

The sync operation polls the target system for synchronization events, that is, native changes to target objects.

The operation has two methods:

  • sync() - request synchronization events from the target system

    This method calls the specified handler, once, to pass back each matching synchronization event. When the method returns, it will no longer invoke the specified handler.

  • getLatestSyncToken() - returns the token corresponding to the most recent synchronization event

Using the ICF Sync Operation

Consumption of the Sync Operation (getLatestSyncToken() Method), at the API Level
@Test
public void getLatestSyncTokenTest() {
    logger.info("Running GetLatestSyncToken Test");
    final ConnectorFacade facade = createConnectorFacade(BasicConnector.class, null);
    SyncToken token = facade.getLatestSyncToken(ObjectClass.ACCOUNT);
    Assert.assertEquals(token.getValue(), 10);
}    

The getLatestSyncToken method throws an IllegalArgumentException if the objectClass is null or invalid.


Consumption of the Sync Operation (sync() Method), at the API Level
@Test
public void syncTest() {
    logger.info("Running Sync Test");
    final ConnectorFacade facade = createConnectorFacade(BasicConnector.class, null);
    final OperationOptionsBuilder builder = new OperationOptionsBuilder();
    builder.setPageSize(10);
    final SyncResultsHandler handler = new SyncResultsHandler() {
        public boolean handle(SyncDelta delta) {
            return false;
        }
    };

    SyncToken token =
            facade.sync(ObjectClass.ACCOUNT, new SyncToken(10), handler, builder.build());
    Assert.assertEquals(token.getValue(), 10);
}    

The sync method throws an IllegalArgumentException if the objectClass or handler is null, or if any argument is invalid.


Implementing the Sync Operation in Your Connector

Implementation of the Sync Operation at the SPI Level
public void sync(ObjectClass objectClass, SyncToken token, SyncResultsHandler handler,
        final OperationOptions options) {
    if (ObjectClass.ALL.equals(objectClass)) {
        //
    } else if (ObjectClass.ACCOUNT.equals(objectClass)) {
        final ConnectorObjectBuilder builder = new ConnectorObjectBuilder();
        builder.setUid("3f50eca0-f5e9-11e3-a3ac-0800200c9a66");
        builder.setName("Foo");
        builder.addAttribute(AttributeBuilder.buildEnabled(true));

        final SyncDeltaBuilder deltaBuilder = new SyncDeltaBuilder();
        deltaBuilder.setObject(builder.build());
        deltaBuilder.setDeltaType(SyncDeltaType.CREATE);
        deltaBuilder.setToken(new SyncToken(10));

        for (SyncDelta connectorObject : CollectionUtil.newSet(deltaBuilder.build())) {
            if (!handler.handle(connectorObject)) {
                // Stop iterating because the handler stopped processing
                break;
            }
        }
    } else {
        logger.warn("Sync of type {0} is not supported", configuration.getConnectorMessages()
                .format(objectClass.getDisplayNameKey(), objectClass.getObjectClassValue()));
        throw new UnsupportedOperationException("Sync of type"
                + objectClass.getObjectClassValue() + " is not supported");
    }
    ((SyncTokenResultsHandler) handler).handleResult(new SyncToken(10));
}

public SyncToken getLatestSyncToken(ObjectClass objectClass) {
    if (ObjectClass.ACCOUNT.equals(objectClass)) {
        return new SyncToken(10);
    } else {
        logger.warn("Sync of type {0} is not supported", configuration.getConnectorMessages()
                .format(objectClass.getDisplayNameKey(), objectClass.getObjectClassValue()));
        throw new UnsupportedOperationException("Sync of type"
                + objectClass.getObjectClassValue() + " is not supported");
    }
}    

Test Operation

The test operation tests the connector configuration. Unlike validation, testing a configuration verifies that every part of the environment that is referred to by the configuration is available. The operation therefore validates that the connection details that are provided in the configuration are accurate, and that the backend is accessible when using them.

For example, the connector might make a physical connection to the host that is specified in the configuration, to check that it exists and that the credentials supplied in the configuration are valid.

The test operation can be invoked before the configuration has been validated, or can validate the configuration before testing it.

Using the ICF Test Operation

At the API level, the test operation throws a RuntimeException if the configuration is not valid, or if the test fails. Your connector implementation should throw the most specific exception available. When no specific exception is available, your connector implementation should throw a ConnectorException.

Consumption of the Test Operation at the API Level
@Test
public void testTest() {
    logger.info("Running Test Test");
    final ConnectorFacade facade = createConnectorFacade(BasicConnector.class, null);
    facade.test();
}

Implementing the Test Operation in Your Connector

Implementation of the Test Operation at the SPI Level
public void test() {
    logger.ok("Test works well");
}

Update Operation

If your connector will allow an authorized caller to update (modify or replace) objects on the target system, you must implement either the update operation, or the "Update Attribute Values Operation". At the API level update operation calls either the UpdateOp or the UpdateAttributeValuesOp, depending on what you have implemented.

The update operation is somewhat simpler to implement than the "Update Attribute Values Operation", because the update attribute values operation must handle any type of update that the caller might specify. However a true implementation of the update attribute values operation offers better performance and atomicity semantics.

Using the ICF Update Operation

At the API level, the update operation returns an UnknownUidException if the UID does not exist on the target system resource and if the connector does not implement the "Update Attribute Values Operation" interface.

Consumption of the Update Operation at the API Level
@Test
public void updateTest() {
    logger.info("Running Update Test");
    final ConnectorFacade facade = createConnectorFacade(BasicConnector.class, null);
    final OperationOptionsBuilder builder = new OperationOptionsBuilder();
    Set<Attribute> updateAttributes = new HashSet<Attribute>();
    updateAttributes.add(new Name("Foo"));

    Uid uid = facade.update(ObjectClass.ACCOUNT, new Uid("Foo"), updateAttributes, builder
                  .build());
    Assert.assertEquals(uid.getUidValue(), "foo");
}

Implementing the Update Operation in Your Connector

At the SPI level, the update operation returns an UnknownUidException if the UID does not exist on the target system.

Implementation of the Update Operation at the SPI Level
public Uid update(ObjectClass objectClass, Uid uid, Set<Attribute> replaceAttributes,
        OperationOptions options) {
    AttributesAccessor attributesAccessor = new AttributesAccessor(replaceAttributes);
    Name newName = attributesAccessor.getName();
    Uid uidAfterUpdate = uid;
    if (newName != null) {
        logger.info("Rename the object {0}:{1} to {2}", objectClass.getObjectClassValue(), uid
                .getUidValue(), newName.getNameValue());
        uidAfterUpdate = new Uid(newName.getNameValue().toLowerCase());
    }

    if (ObjectClass.ACCOUNT.equals(objectClass)) {

    } else if (ObjectClass.GROUP.is(objectClass.getObjectClassValue())) {
        if (attributesAccessor.hasAttribute("members")) {
            throw new InvalidAttributeValueException(
                    "Requested to update a read only attribute");
        }
    } else {
        logger.warn("Update of type {0} is not supported", configuration.getConnectorMessages()
                .format(objectClass.getDisplayNameKey(), objectClass.getObjectClassValue()));
        throw new UnsupportedOperationException("Update of type"
                + objectClass.getObjectClassValue() + " is not supported");
    }
    return uidAfterUpdate;
}    

Suggested Approach for Deleting Attributes and Removing Attribute Values

If the target resource to which you are connecting supports the removal of attributes, you can implement the removal in several ways. All the samples in this document assume the following syntax rules for deleting attributes or removing their values.

UpdateSyntax ruleQuery filter
Set an empty attribute value[""] (application sends an attribute value that is a list containing one empty string)equal=""
Set an attribute value to null[] (application sends an attribute value that is an empty list)ispresent search returns 1
Removing an attributenull (application sends an attribute value that is nullispresent search returns 1

Update Attribute Values Operation

The update attribute values operation is an advanced implementation of the update operation. You should implement this operation if you want your connector to offer better performance and atomicity for the following methods:

  • UpdateApiOp.addAttributeValues(ObjectClass, Uid, Set, OperationOptions)

  • UpdateApiOp.removeAttributeValues(ObjectClass, Uid, Set, OperationOptions)

Consumption of the Add and Remove Attribute Values Methods at the API Level
@Test
public void addAttributeValuesTest() {
    logger.info("Running AddAttributeValues Test");
    final ConnectorFacade facade = createConnectorFacade(BasicConnector.class, null);
    final OperationOptionsBuilder builder = new OperationOptionsBuilder();
    Set<Attribute> updateAttributes = new HashSet<Attribute>();
    // add 'group2' to existing groups
    updateAttributes.add(AttributeBuilder.build(PredefinedAttributes.GROUPS_NAME, "group2"));

    Uid uid =
            facade.addAttributeValues(ObjectClass.ACCOUNT, new Uid("Foo"), updateAttributes,
                    builder.build());
    Assert.assertEquals(uid.getUidValue(), "foo");
}

@Test
public void removeAttributeValuesTest() {
    logger.info("Running RemoveAttributeValues Test");
    final ConnectorFacade facade = createConnectorFacade(BasicConnector.class, null);
    final OperationOptionsBuilder builder = new OperationOptionsBuilder();
    Set<Attribute> updateAttributes = new HashSet<Attribute>();
    // remove 'group2' from existing groups
    updateAttributes.add(AttributeBuilder.build(PredefinedAttributes.GROUPS_NAME, "group2"));

    Uid uid =
            facade.removeAttributeValues(ObjectClass.ACCOUNT, new Uid("Foo"), updateAttributes,
                    builder.build());
    Assert.assertEquals(uid.getUidValue(), "foo");
}

Implementing the Update Attribute Values Operation in Your Connector

At the SPI level, the update attribute values operation returns an UnknownUidException when the UID does not exist on the resource.

Implementation of the update attribute values operation, at the SPI Level
public Uid addAttributeValues(ObjectClass objectClass, Uid uid, Set<Attribute> valuesToAdd,
        OperationOptions options) {
    return uid;
}

public Uid removeAttributeValues(ObjectClass objectClass, Uid uid,
        Set<Attribute> valuesToRemove, OperationOptions options) {
    return uid;
}    

Read a different version of :