com.evolveum.icf.dummy.connector.DummyConnector.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.icf.dummy.connector.DummyConnector.java

Source

/*
 * Copyright (c) 2010-2015 Evolveum
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.evolveum.icf.dummy.connector;

import org.apache.commons.lang.StringUtils;
import org.identityconnectors.framework.spi.operations.*;
import org.identityconnectors.framework.common.exceptions.AlreadyExistsException;
import org.identityconnectors.framework.common.exceptions.ConnectionFailedException;
import org.identityconnectors.framework.common.exceptions.ConnectorException;
import org.identityconnectors.framework.common.exceptions.ConnectorIOException;
import org.identityconnectors.framework.common.exceptions.UnknownUidException;
import org.identityconnectors.framework.common.objects.*;

import static com.evolveum.icf.dummy.connector.Utils.*;

import java.io.FileNotFoundException;
import java.net.ConnectException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.identityconnectors.common.logging.Log;
import org.identityconnectors.common.security.GuardedString;
import org.identityconnectors.common.security.GuardedString.Accessor;
import org.identityconnectors.framework.common.objects.Attribute;
import org.identityconnectors.framework.common.objects.AttributeUtil;
import org.identityconnectors.framework.common.objects.ConnectorObject;
import org.identityconnectors.framework.common.objects.ConnectorObjectBuilder;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.identityconnectors.framework.common.objects.ObjectClassInfo;
import org.identityconnectors.framework.common.objects.ObjectClassInfoBuilder;
import org.identityconnectors.framework.common.objects.OperationOptions;
import org.identityconnectors.framework.common.objects.ResultsHandler;
import org.identityconnectors.framework.common.objects.Schema;
import org.identityconnectors.framework.common.objects.SchemaBuilder;
import org.identityconnectors.framework.common.objects.SyncDelta;
import org.identityconnectors.framework.common.objects.SyncDeltaBuilder;
import org.identityconnectors.framework.common.objects.SyncResultsHandler;
import org.identityconnectors.framework.common.objects.SyncToken;
import org.identityconnectors.framework.common.objects.Uid;
import org.identityconnectors.framework.common.objects.filter.AndFilter;
import org.identityconnectors.framework.common.objects.filter.AttributeFilter;
import org.identityconnectors.framework.common.objects.filter.ContainsAllValuesFilter;
import org.identityconnectors.framework.common.objects.filter.ContainsFilter;
import org.identityconnectors.framework.common.objects.filter.EndsWithFilter;
import org.identityconnectors.framework.common.objects.filter.EqualsFilter;
import org.identityconnectors.framework.common.objects.filter.Filter;
import org.identityconnectors.framework.common.objects.filter.FilterTranslator;
import org.identityconnectors.framework.common.objects.filter.GreaterThanFilter;
import org.identityconnectors.framework.common.objects.filter.GreaterThanOrEqualFilter;
import org.identityconnectors.framework.common.objects.filter.LessThanFilter;
import org.identityconnectors.framework.common.objects.filter.LessThanOrEqualFilter;
import org.identityconnectors.framework.common.objects.filter.NotFilter;
import org.identityconnectors.framework.common.objects.filter.OrFilter;
import org.identityconnectors.framework.common.objects.filter.StartsWithFilter;
import org.identityconnectors.framework.spi.Configuration;
import org.identityconnectors.framework.spi.Connector;
import org.identityconnectors.framework.spi.ConnectorClass;
import org.identityconnectors.framework.spi.PoolableConnector;
import org.identityconnectors.framework.spi.operations.AuthenticateOp;
import org.identityconnectors.framework.spi.operations.CreateOp;
import org.identityconnectors.framework.spi.operations.DeleteOp;
import org.identityconnectors.framework.spi.operations.ResolveUsernameOp;
import org.identityconnectors.framework.spi.operations.SchemaOp;
import org.identityconnectors.framework.spi.operations.SearchOp;
import org.identityconnectors.framework.spi.operations.SyncOp;
import org.identityconnectors.framework.spi.operations.TestOp;
import org.identityconnectors.framework.spi.operations.UpdateAttributeValuesOp;

import com.evolveum.icf.dummy.resource.DummyAccount;
import com.evolveum.icf.dummy.resource.DummyAttributeDefinition;
import com.evolveum.icf.dummy.resource.DummyDelta;
import com.evolveum.icf.dummy.resource.DummyDeltaType;
import com.evolveum.icf.dummy.resource.DummyGroup;
import com.evolveum.icf.dummy.resource.DummyObject;
import com.evolveum.icf.dummy.resource.DummyObjectClass;
import com.evolveum.icf.dummy.resource.DummyPrivilege;
import com.evolveum.icf.dummy.resource.DummyResource;
import com.evolveum.icf.dummy.resource.DummySyncStyle;
import com.evolveum.icf.dummy.resource.ObjectAlreadyExistsException;
import com.evolveum.icf.dummy.resource.ObjectDoesNotExistException;
import com.evolveum.icf.dummy.resource.SchemaViolationException;

/**
 * Connector for the Dummy Resource.
 * 
 * Dummy resource is a simple Java object that pretends to be a resource. It has accounts and
 * account schema. It has operations to manipulate accounts, execute scripts and so on
 * almost like a real resource. The purpose is to simulate a real resource with a very 
 * little overhead. This connector connects the Dummy resource to ICF.
 * 
 * @see DummyResource
 *
 * @author $author$
 * @version $Revision$ $Date$
 */
@ConnectorClass(displayNameKey = "UI_CONNECTOR_NAME", configurationClass = DummyConfiguration.class)
public class DummyConnector
        implements PoolableConnector, AuthenticateOp, ResolveUsernameOp, CreateOp, DeleteOp, SchemaOp,
        ScriptOnConnectorOp, ScriptOnResourceOp, SearchOp<Filter>, SyncOp, TestOp, UpdateAttributeValuesOp {

    // We want to see if the ICF framework logging works properly
    private static final Log log = Log.getLog(DummyConnector.class);
    // We also want to see if the libraries that use JUL are logging properly
    private static final java.util.logging.Logger julLogger = java.util.logging.Logger
            .getLogger(DummyConnector.class.getName());

    // Marker used in logging tests
    public static final String LOG_MARKER = "_M_A_R_K_E_R_";

    private static final String OBJECTCLASS_ACCOUNT_NAME = "account";
    private static final String OBJECTCLASS_GROUP_NAME = "group";
    private static final String OBJECTCLASS_PRIVILEGE_NAME = "privilege";

    /**
     * Place holder for the {@link Configuration} passed into the init() method
     */
    private DummyConfiguration configuration;

    private DummyResource resource;

    /**
     * Gets the Configuration context for this connector.
     */
    @Override
    public Configuration getConfiguration() {
        return this.configuration;
    }

    /**
     * Callback method to receive the {@link Configuration}.
     *
     * @see Connector#init(org.identityconnectors.framework.spi.Configuration)
     */
    @Override
    public void init(Configuration configuration) {
        notNullArgument(configuration, "configuration");
        this.configuration = (DummyConfiguration) configuration;

        String instanceName = this.configuration.getInstanceId();
        if (instanceName == null || instanceName.isEmpty()) {
            instanceName = null;
        }
        resource = DummyResource.getInstance(instanceName);

        resource.setCaseIgnoreId(this.configuration.getCaseIgnoreId());
        resource.setCaseIgnoreValues(this.configuration.getCaseIgnoreValues());
        resource.setEnforceUniqueName(this.configuration.isEnforceUniqueName());
        resource.setTolerateDuplicateValues(this.configuration.getTolerateDuplicateValues());
        resource.setGenerateDefaultValues(this.configuration.isGenerateDefaultValues());
        resource.setGenerateAccountDescriptionOnCreate(this.configuration.getGenerateAccountDescriptionOnCreate());
        resource.setGenerateAccountDescriptionOnUpdate(this.configuration.getGenerateAccountDescriptionOnUpdate());
        if (this.configuration.getForbiddenNames().length > 0) {
            resource.setForbiddenNames(Arrays.asList(((DummyConfiguration) configuration).getForbiddenNames()));
        } else {
            resource.setForbiddenNames(null);
        }

        resource.setUselessString(this.configuration.getUselessString());
        GuardedString uselessGuardedString = this.configuration.getUselessGuardedString();
        if (uselessGuardedString == null) {
            resource.setUselessGuardedString(null);
        } else {
            uselessGuardedString.access(new GuardedString.Accessor() {
                @Override
                public void access(char[] chars) {
                    resource.setUselessGuardedString(new String(chars));
                }
            });
        }

        resource.connect();

        log.info("Connected to dummy resource instance {0} ({1} connections open)", resource,
                resource.getConnectionCount());
    }

    /**
     * Disposes of the {@link CSVFileConnector}'s resources.
     *
     * @see Connector#dispose()
     */
    public void dispose() {
        resource.disconnect();
        log.info("Disconnected from dummy resource instance {0} ({1} connections still open)", resource,
                resource.getConnectionCount());
    }

    @Override
    public void checkAlive() {
        // notthig to do. always alive.
    }

    /******************
     * SPI Operations
     *
     * Implement the following operations using the contract and
     * description found in the Javadoc for these methods.
     ******************/

    /**
     * {@inheritDoc}
     */

    /**
     * {@inheritDoc}
     */
    public Uid create(final ObjectClass objectClass, final Set<Attribute> createAttributes,
            final OperationOptions options) {
        log.info("create::begin attributes {0}", createAttributes);
        validate(objectClass);

        DummyObject newObject;
        try {

            if (ObjectClass.ACCOUNT.is(objectClass.getObjectClassValue())) {
                // Convert attributes to account
                DummyAccount newAccount = convertToAccount(createAttributes);

                log.ok("Adding dummy account:\n{0}", newAccount.debugDump());

                resource.addAccount(newAccount);
                newObject = newAccount;

            } else if (ObjectClass.GROUP.is(objectClass.getObjectClassValue())) {
                DummyGroup newGroup = convertToGroup(createAttributes);

                log.ok("Adding dummy group:\n{0}", newGroup.debugDump());

                resource.addGroup(newGroup);
                newObject = newGroup;

            } else if (objectClass.is(OBJECTCLASS_PRIVILEGE_NAME)) {
                DummyPrivilege newPriv = convertToPriv(createAttributes);

                log.ok("Adding dummy privilege:\n{0}", newPriv.debugDump());

                resource.addPrivilege(newPriv);
                newObject = newPriv;

            } else {
                throw new ConnectorException("Unknown object class " + objectClass);
            }

        } catch (ObjectAlreadyExistsException e) {
            // Note: let's do the bad thing and add exception loaded by this classloader as inner exception here
            // The framework should deal with it ... somehow
            throw new AlreadyExistsException(e.getMessage(), e);
        } catch (ConnectException e) {
            throw new ConnectionFailedException(e.getMessage(), e);
        } catch (FileNotFoundException e) {
            throw new ConnectorIOException(e.getMessage(), e);
        } catch (SchemaViolationException e) {
            throw new ConnectorException(e);
        }

        String id;
        if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_NAME)) {
            id = newObject.getName();
        } else if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_UUID)) {
            id = newObject.getId();
        } else {
            throw new IllegalStateException("Unknown UID mode " + configuration.getUidMode());
        }
        Uid uid = new Uid(id);

        log.info("create::end");
        return uid;
    }

    /**
      * {@inheritDoc}
      */
    public Uid update(ObjectClass objectClass, Uid uid, Set<Attribute> replaceAttributes,
            OperationOptions options) {
        log.info("update::begin");
        validate(objectClass);

        try {

            if (ObjectClass.ACCOUNT.is(objectClass.getObjectClassValue())) {

                final DummyAccount account;
                if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_NAME)) {
                    account = resource.getAccountByUsername(uid.getUidValue());
                } else if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_UUID)) {
                    account = resource.getAccountById(uid.getUidValue());
                } else {
                    throw new IllegalStateException("Unknown UID mode " + configuration.getUidMode());
                }
                if (account == null) {
                    throw new UnknownUidException("Account with UID " + uid + " does not exist on resource");
                }

                // we do this before setting attribute values, in case when description itself would be changed
                resource.changeDescriptionIfNeeded(account);

                for (Attribute attr : replaceAttributes) {
                    if (attr.is(Name.NAME)) {
                        String newName = (String) attr.getValue().get(0);
                        try {
                            resource.renameAccount(account.getId(), account.getName(), newName);
                        } catch (ObjectDoesNotExistException e) {
                            throw new org.identityconnectors.framework.common.exceptions.UnknownUidException(
                                    e.getMessage(), e);
                        } catch (ObjectAlreadyExistsException e) {
                            throw new org.identityconnectors.framework.common.exceptions.AlreadyExistsException(
                                    e.getMessage(), e);
                        } catch (SchemaViolationException e) {
                            throw new org.identityconnectors.framework.common.exceptions.ConnectorException(
                                    "Schema exception: " + e.getMessage(), e);
                        }
                        // We need to change the returned uid here (only if the mode is not set to UUID)
                        if (!(configuration.getUidMode().equals(DummyConfiguration.UID_MODE_UUID))) {
                            uid = new Uid(newName);
                        }
                    } else if (attr.is(OperationalAttributes.PASSWORD_NAME)) {
                        changePassword(account, attr);

                    } else if (attr.is(OperationalAttributes.ENABLE_NAME)) {
                        account.setEnabled(getBoolean(attr));

                    } else if (attr.is(OperationalAttributes.ENABLE_DATE_NAME)) {
                        account.setValidFrom(getDate(attr));

                    } else if (attr.is(OperationalAttributes.DISABLE_DATE_NAME)) {
                        account.setValidTo(getDate(attr));

                    } else if (attr.is(OperationalAttributes.LOCK_OUT_NAME)) {
                        account.setLockout(getBoolean(attr));

                    } else {
                        String name = attr.getName();
                        try {
                            account.replaceAttributeValues(name, attr.getValue());
                        } catch (SchemaViolationException e) {
                            // we cannot throw checked exceptions. But this one looks suitable.
                            // Note: let's do the bad thing and add exception loaded by this classloader as inner exception here
                            // The framework should deal with it ... somehow
                            throw new IllegalArgumentException(e.getMessage(), e);
                        }
                    }
                }

            } else if (ObjectClass.GROUP.is(objectClass.getObjectClassValue())) {

                final DummyGroup group;
                if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_NAME)) {
                    group = resource.getGroupByName(uid.getUidValue());
                } else if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_UUID)) {
                    group = resource.getGroupById(uid.getUidValue());
                } else {
                    throw new IllegalStateException("Unknown UID mode " + configuration.getUidMode());
                }
                if (group == null) {
                    throw new UnknownUidException("Group with UID " + uid + " does not exist on resource");
                }

                for (Attribute attr : replaceAttributes) {
                    if (attr.is(Name.NAME)) {
                        String newName = (String) attr.getValue().get(0);
                        try {
                            resource.renameGroup(group.getId(), group.getName(), newName);
                        } catch (ObjectDoesNotExistException e) {
                            throw new org.identityconnectors.framework.common.exceptions.UnknownUidException(
                                    e.getMessage(), e);
                        } catch (ObjectAlreadyExistsException e) {
                            throw new org.identityconnectors.framework.common.exceptions.AlreadyExistsException(
                                    e.getMessage(), e);
                        }
                        // We need to change the returned uid here
                        uid = new Uid(newName);
                    } else if (attr.is(OperationalAttributes.PASSWORD_NAME)) {
                        throw new IllegalArgumentException("Attempt to change password on group");

                    } else if (attr.is(OperationalAttributes.ENABLE_NAME)) {
                        group.setEnabled(getBoolean(attr));

                    } else {
                        String name = attr.getName();
                        List<Object> values = attr.getValue();
                        if (attr.is(DummyGroup.ATTR_MEMBERS_NAME) && values != null
                                && configuration.getUpCaseName()) {
                            List<Object> newValues = new ArrayList<Object>(values.size());
                            for (Object val : values) {
                                newValues.add(StringUtils.upperCase((String) val));
                            }
                            values = newValues;
                        }
                        try {
                            group.replaceAttributeValues(name, values);
                        } catch (SchemaViolationException e) {
                            throw new IllegalArgumentException(e.getMessage(), e);
                        }
                    }
                }

            } else if (objectClass.is(OBJECTCLASS_PRIVILEGE_NAME)) {

                final DummyPrivilege priv;
                if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_NAME)) {
                    priv = resource.getPrivilegeByName(uid.getUidValue());
                } else if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_UUID)) {
                    priv = resource.getPrivilegeById(uid.getUidValue());
                } else {
                    throw new IllegalStateException("Unknown UID mode " + configuration.getUidMode());
                }
                if (priv == null) {
                    throw new UnknownUidException("Privilege with UID " + uid + " does not exist on resource");
                }

                for (Attribute attr : replaceAttributes) {
                    if (attr.is(Name.NAME)) {
                        String newName = (String) attr.getValue().get(0);
                        try {
                            resource.renamePrivilege(priv.getId(), priv.getName(), newName);
                        } catch (ObjectDoesNotExistException e) {
                            throw new org.identityconnectors.framework.common.exceptions.UnknownUidException(
                                    e.getMessage(), e);
                        } catch (ObjectAlreadyExistsException e) {
                            throw new org.identityconnectors.framework.common.exceptions.AlreadyExistsException(
                                    e.getMessage(), e);
                        }
                        // We need to change the returned uid here
                        uid = new Uid(newName);
                    } else if (attr.is(OperationalAttributes.PASSWORD_NAME)) {
                        throw new IllegalArgumentException("Attempt to change password on privilege");

                    } else if (attr.is(OperationalAttributes.ENABLE_NAME)) {
                        throw new IllegalArgumentException("Attempt to change enable on privilege");

                    } else {
                        String name = attr.getName();
                        try {
                            priv.replaceAttributeValues(name, attr.getValue());
                        } catch (SchemaViolationException e) {
                            throw new IllegalArgumentException(e.getMessage(), e);
                        }
                    }
                }

            } else {
                throw new ConnectorException("Unknown object class " + objectClass);
            }

        } catch (ConnectException e) {
            log.info("update::exception " + e);
            throw new ConnectionFailedException(e.getMessage(), e);
        } catch (FileNotFoundException e) {
            log.info("update::exception " + e);
            throw new ConnectorIOException(e.getMessage(), e);
        }

        log.info("update::end");
        return uid;
    }

    /**
      * {@inheritDoc}
      */
    public Uid addAttributeValues(ObjectClass objectClass, Uid uid, Set<Attribute> valuesToAdd,
            OperationOptions options) {
        log.info("addAttributeValues::begin");
        validate(objectClass);

        try {

            if (ObjectClass.ACCOUNT.is(objectClass.getObjectClassValue())) {

                DummyAccount account;
                if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_NAME)) {
                    account = resource.getAccountByUsername(uid.getUidValue());
                } else if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_UUID)) {
                    account = resource.getAccountById(uid.getUidValue());
                } else {
                    throw new IllegalStateException("Unknown UID mode " + configuration.getUidMode());
                }
                if (account == null) {
                    throw new UnknownUidException("Account with UID " + uid + " does not exist on resource");
                }

                // we could change the description here, but don't do that not to collide with ADD operation
                // TODO add the functionality if needed

                for (Attribute attr : valuesToAdd) {

                    if (attr.is(OperationalAttributeInfos.PASSWORD.getName())) {
                        if (account.getPassword() != null) {
                            throw new IllegalArgumentException(
                                    "Attempt to add value for password while password is already set");
                        }
                        changePassword(account, attr);

                    } else if (attr.is(OperationalAttributes.ENABLE_NAME)) {
                        throw new IllegalArgumentException("Attempt to add value for enable attribute");

                    } else {
                        String name = attr.getName();
                        try {
                            account.addAttributeValues(name, attr.getValue());
                        } catch (SchemaViolationException e) {
                            // we cannot throw checked exceptions. But this one looks suitable.
                            // Note: let's do the bad thing and add exception loaded by this classloader as inner exception here
                            // The framework should deal with it ... somehow
                            throw new IllegalArgumentException(e.getMessage(), e);
                        }
                    }
                }

            } else if (ObjectClass.GROUP.is(objectClass.getObjectClassValue())) {

                DummyGroup group;
                if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_NAME)) {
                    group = resource.getGroupByName(uid.getUidValue());
                } else if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_UUID)) {
                    group = resource.getGroupById(uid.getUidValue());
                } else {
                    throw new IllegalStateException("Unknown UID mode " + configuration.getUidMode());
                }
                if (group == null) {
                    throw new UnknownUidException("Group with UID " + uid + " does not exist on resource");
                }

                for (Attribute attr : valuesToAdd) {

                    if (attr.is(OperationalAttributeInfos.PASSWORD.getName())) {
                        throw new IllegalArgumentException("Attempt to change password on group");

                    } else if (attr.is(OperationalAttributes.ENABLE_NAME)) {
                        throw new IllegalArgumentException("Attempt to add value for enable attribute");

                    } else {
                        String name = attr.getName();
                        List<Object> values = attr.getValue();
                        if (attr.is(DummyGroup.ATTR_MEMBERS_NAME) && values != null
                                && configuration.getUpCaseName()) {
                            List<Object> newValues = new ArrayList<Object>(values.size());
                            for (Object val : values) {
                                newValues.add(StringUtils.upperCase((String) val));
                            }
                            values = newValues;
                        }
                        try {
                            group.addAttributeValues(name, values);
                        } catch (SchemaViolationException e) {
                            // we cannot throw checked exceptions. But this one looks suitable.
                            // Note: let's do the bad thing and add exception loaded by this classloader as inner exception here
                            // The framework should deal with it ... somehow
                            throw new IllegalArgumentException(e.getMessage(), e);
                        }
                    }
                }

            } else if (objectClass.is(OBJECTCLASS_PRIVILEGE_NAME)) {

                DummyPrivilege priv;
                if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_NAME)) {
                    priv = resource.getPrivilegeByName(uid.getUidValue());
                } else if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_UUID)) {
                    priv = resource.getPrivilegeById(uid.getUidValue());
                } else {
                    throw new IllegalStateException("Unknown UID mode " + configuration.getUidMode());
                }
                if (priv == null) {
                    throw new UnknownUidException("Privilege with UID " + uid + " does not exist on resource");
                }

                for (Attribute attr : valuesToAdd) {

                    if (attr.is(OperationalAttributeInfos.PASSWORD.getName())) {
                        throw new IllegalArgumentException("Attempt to change password on privilege");

                    } else if (attr.is(OperationalAttributes.ENABLE_NAME)) {
                        throw new IllegalArgumentException("Attempt to add value for enable attribute");

                    } else {
                        String name = attr.getName();
                        try {
                            priv.addAttributeValues(name, attr.getValue());
                        } catch (SchemaViolationException e) {
                            // we cannot throw checked exceptions. But this one looks suitable.
                            // Note: let's do the bad thing and add exception loaded by this classloader as inner exception here
                            // The framework should deal with it ... somehow
                            throw new IllegalArgumentException(e.getMessage(), e);
                        }
                    }
                }

            } else {
                throw new ConnectorException("Unknown object class " + objectClass);
            }

        } catch (ConnectException e) {
            log.info("addAttributeValues::exception " + e);
            throw new ConnectionFailedException(e.getMessage(), e);
        } catch (FileNotFoundException e) {
            log.info("addAttributeValues::exception " + e);
            throw new ConnectorIOException(e.getMessage(), e);
        }

        log.info("addAttributeValues::end");
        return uid;
    }

    /**
     * {@inheritDoc}
     */
    public Uid removeAttributeValues(ObjectClass objectClass, Uid uid, Set<Attribute> valuesToRemove,
            OperationOptions options) {
        log.info("removeAttributeValues::begin");
        validate(objectClass);

        try {

            if (ObjectClass.ACCOUNT.is(objectClass.getObjectClassValue())) {

                DummyAccount account;
                if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_NAME)) {
                    account = resource.getAccountByUsername(uid.getUidValue());
                } else if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_UUID)) {
                    account = resource.getAccountById(uid.getUidValue());
                } else {
                    throw new IllegalStateException("Unknown UID mode " + configuration.getUidMode());
                }
                if (account == null) {
                    throw new UnknownUidException("Account with UID " + uid + " does not exist on resource");
                }

                // we could change the description here, but don't do that not to collide with REMOVE operation
                // TODO add the functionality if needed

                for (Attribute attr : valuesToRemove) {
                    if (attr.is(OperationalAttributeInfos.PASSWORD.getName())) {
                        throw new UnsupportedOperationException("Removing password value is not supported");
                    } else if (attr.is(OperationalAttributes.ENABLE_NAME)) {
                        throw new IllegalArgumentException("Attempt to remove value from enable attribute");
                    } else {
                        String name = attr.getName();
                        try {
                            account.removeAttributeValues(name, attr.getValue());
                        } catch (SchemaViolationException e) {
                            // we cannot throw checked exceptions. But this one looks suitable.
                            // Note: let's do the bad thing and add exception loaded by this classloader as inner exception here
                            // The framework should deal with it ... somehow
                            throw new IllegalArgumentException(e.getMessage(), e);
                        }
                    }
                }

            } else if (ObjectClass.GROUP.is(objectClass.getObjectClassValue())) {

                DummyGroup group;
                if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_NAME)) {
                    group = resource.getGroupByName(uid.getUidValue());
                } else if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_UUID)) {
                    group = resource.getGroupById(uid.getUidValue());
                } else {
                    throw new IllegalStateException("Unknown UID mode " + configuration.getUidMode());
                }
                if (group == null) {
                    throw new UnknownUidException("Group with UID " + uid + " does not exist on resource");
                }

                for (Attribute attr : valuesToRemove) {
                    if (attr.is(OperationalAttributeInfos.PASSWORD.getName())) {
                        throw new IllegalArgumentException("Attempt to change password on group");
                    } else if (attr.is(OperationalAttributes.ENABLE_NAME)) {
                        throw new IllegalArgumentException("Attempt to remove value from enable attribute");
                    } else {
                        String name = attr.getName();
                        List<Object> values = attr.getValue();
                        if (attr.is(DummyGroup.ATTR_MEMBERS_NAME) && values != null
                                && configuration.getUpCaseName()) {
                            List<Object> newValues = new ArrayList<Object>(values.size());
                            for (Object val : values) {
                                newValues.add(StringUtils.upperCase((String) val));
                            }
                            values = newValues;
                        }
                        try {
                            group.removeAttributeValues(name, values);
                        } catch (SchemaViolationException e) {
                            // we cannot throw checked exceptions. But this one looks suitable.
                            // Note: let's do the bad thing and add exception loaded by this classloader as inner exception here
                            // The framework should deal with it ... somehow
                            throw new IllegalArgumentException(e.getMessage(), e);
                        }
                    }
                }

            } else if (objectClass.is(OBJECTCLASS_PRIVILEGE_NAME)) {

                DummyPrivilege priv;
                if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_NAME)) {
                    priv = resource.getPrivilegeByName(uid.getUidValue());
                } else if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_UUID)) {
                    priv = resource.getPrivilegeById(uid.getUidValue());
                } else {
                    throw new IllegalStateException("Unknown UID mode " + configuration.getUidMode());
                }
                if (priv == null) {
                    throw new UnknownUidException("Privilege with UID " + uid + " does not exist on resource");
                }

                for (Attribute attr : valuesToRemove) {
                    if (attr.is(OperationalAttributeInfos.PASSWORD.getName())) {
                        throw new IllegalArgumentException("Attempt to change password on privilege");
                    } else if (attr.is(OperationalAttributes.ENABLE_NAME)) {
                        throw new IllegalArgumentException("Attempt to remove value from enable attribute");
                    } else {
                        String name = attr.getName();
                        try {
                            priv.removeAttributeValues(name, attr.getValue());
                        } catch (SchemaViolationException e) {
                            // we cannot throw checked exceptions. But this one looks suitable.
                            // Note: let's do the bad thing and add exception loaded by this classloader as inner exception here
                            // The framework should deal with it ... somehow
                            throw new IllegalArgumentException(e.getMessage(), e);
                        }
                    }
                }

            } else {
                throw new ConnectorException("Unknown object class " + objectClass);
            }

        } catch (ConnectException e) {
            log.info("removeAttributeValues::exception " + e);
            throw new ConnectionFailedException(e.getMessage(), e);
        } catch (FileNotFoundException e) {
            log.info("removeAttributeValues::exception " + e);
            throw new ConnectorIOException(e.getMessage(), e);
        }

        log.info("removeAttributeValues::end");
        return uid;
    }

    /**
      * {@inheritDoc}
      */
    public void delete(final ObjectClass objectClass, final Uid uid, final OperationOptions options) {
        log.info("delete::begin");
        validate(objectClass);

        String id = uid.getUidValue();

        try {
            if (ObjectClass.ACCOUNT.is(objectClass.getObjectClassValue())) {
                if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_NAME)) {
                    resource.deleteAccountByName(id);
                } else if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_UUID)) {
                    resource.deleteAccountById(id);
                } else {
                    throw new IllegalStateException("Unknown UID mode " + configuration.getUidMode());
                }
            } else if (ObjectClass.GROUP.is(objectClass.getObjectClassValue())) {
                if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_NAME)) {
                    resource.deleteGroupByName(id);
                } else if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_UUID)) {
                    resource.deleteGroupById(id);
                } else {
                    throw new IllegalStateException("Unknown UID mode " + configuration.getUidMode());
                }
            } else if (objectClass.is(OBJECTCLASS_PRIVILEGE_NAME)) {
                if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_NAME)) {
                    resource.deletePrivilegeByName(id);
                } else if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_UUID)) {
                    resource.deletePrivilegeById(id);
                } else {
                    throw new IllegalStateException("Unknown UID mode " + configuration.getUidMode());
                }

            } else {
                throw new ConnectorException("Unknown object class " + objectClass);
            }

        } catch (ObjectDoesNotExistException e) {
            // we cannot throw checked exceptions. But this one looks suitable.
            // Note: let's do the bad thing and add exception loaded by this classloader as inner exception here
            // The framework should deal with it ... somehow
            throw new UnknownUidException(e.getMessage(), e);
        } catch (ConnectException e) {
            log.info("delete::exception " + e);
            throw new ConnectionFailedException(e.getMessage(), e);
        } catch (FileNotFoundException e) {
            log.info("delete::exception " + e);
            throw new ConnectorIOException(e.getMessage(), e);
        }

        log.info("delete::end");
    }

    /**
     * {@inheritDoc}
     */
    public Schema schema() {
        log.info("schema::begin");

        if (!configuration.getSupportSchema()) {
            log.info("schema::unsupported operation");
            throw new UnsupportedOperationException();
        }

        SchemaBuilder builder = new SchemaBuilder(DummyConnector.class);

        builder.defineObjectClass(createAccountObjectClass(configuration.getSupportActivation()));
        builder.defineObjectClass(createGroupObjectClass(configuration.getSupportActivation()));
        builder.defineObjectClass(createPrivilegeObjectClass());

        log.info("schema::end");
        return builder.build();
    }

    private String getAccountObjectClassName() {
        if (configuration.getUseLegacySchema()) {
            return ObjectClass.ACCOUNT_NAME;
        } else {
            return OBJECTCLASS_ACCOUNT_NAME;
        }
    }

    private String getGroupObjectClassName() {
        if (configuration.getUseLegacySchema()) {
            return ObjectClass.GROUP_NAME;
        } else {
            return OBJECTCLASS_GROUP_NAME;
        }
    }

    private ObjectClassInfoBuilder createCommonObjectClassBuilder(String typeName,
            DummyObjectClass dummyAccountObjectClass, boolean supportsActivation) {
        ObjectClassInfoBuilder objClassBuilder = new ObjectClassInfoBuilder();
        if (typeName != null) {
            objClassBuilder.setType(typeName);
        }

        buildAttributes(objClassBuilder, dummyAccountObjectClass);

        if (supportsActivation) {
            // __ENABLE__ attribute
            objClassBuilder.addAttributeInfo(OperationalAttributeInfos.ENABLE);

            if (configuration.getSupportValidity()) {
                objClassBuilder.addAttributeInfo(OperationalAttributeInfos.ENABLE_DATE);
                objClassBuilder.addAttributeInfo(OperationalAttributeInfos.DISABLE_DATE);
            }

            objClassBuilder.addAttributeInfo(OperationalAttributeInfos.LOCK_OUT);
        }

        // __NAME__ will be added by default
        return objClassBuilder;
    }

    private ObjectClassInfo createAccountObjectClass(boolean supportsActivation) {
        // __ACCOUNT__ objectclass

        DummyObjectClass dummyAccountObjectClass;
        try {
            dummyAccountObjectClass = resource.getAccountObjectClass();
        } catch (ConnectException e) {
            throw new ConnectionFailedException(e.getMessage(), e);
        } catch (FileNotFoundException e) {
            throw new ConnectorIOException(e.getMessage(), e);
        } catch (IllegalArgumentException e) {
            throw new ConnectorException(e.getMessage(), e);
        } // DO NOT catch IllegalStateException, let it pass

        ObjectClassInfoBuilder objClassBuilder = createCommonObjectClassBuilder(getAccountObjectClassName(),
                dummyAccountObjectClass, supportsActivation);

        // __PASSWORD__ attribute
        objClassBuilder.addAttributeInfo(OperationalAttributeInfos.PASSWORD);

        return objClassBuilder.build();
    }

    private ObjectClassInfo createGroupObjectClass(boolean supportsActivation) {
        // __GROUP__ objectclass
        ObjectClassInfoBuilder objClassBuilder = createCommonObjectClassBuilder(getGroupObjectClassName(),
                resource.getGroupObjectClass(), supportsActivation);

        return objClassBuilder.build();
    }

    private ObjectClassInfo createPrivilegeObjectClass() {
        ObjectClassInfoBuilder objClassBuilder = createCommonObjectClassBuilder(OBJECTCLASS_PRIVILEGE_NAME,
                resource.getPrivilegeObjectClass(), false);
        return objClassBuilder.build();
    }

    private void buildAttributes(ObjectClassInfoBuilder icfObjClassBuilder, DummyObjectClass dummyObjectClass) {
        for (DummyAttributeDefinition dummyAttrDef : dummyObjectClass.getAttributeDefinitions()) {
            AttributeInfoBuilder attrBuilder = new AttributeInfoBuilder(dummyAttrDef.getAttributeName(),
                    dummyAttrDef.getAttributeType());
            attrBuilder.setMultiValued(dummyAttrDef.isMulti());
            attrBuilder.setRequired(dummyAttrDef.isRequired());
            attrBuilder.setReturnedByDefault(dummyAttrDef.isReturnedByDefault());
            icfObjClassBuilder.addAttributeInfo(attrBuilder.build());
        }
    }

    /**
      * {@inheritDoc}
      */
    public Uid authenticate(final ObjectClass objectClass, final String userName, final GuardedString password,
            final OperationOptions options) {
        log.info("authenticate::begin");
        Uid uid = null;
        log.info("authenticate::end");
        return uid;
    }

    /**
     * {@inheritDoc}
     */
    public Uid resolveUsername(final ObjectClass objectClass, final String userName,
            final OperationOptions options) {
        log.info("resolveUsername::begin");
        Uid uid = null;
        log.info("resolveUsername::end");
        return uid;
    }

    /**
     * {@inheritDoc}
     */
    public Object runScriptOnConnector(ScriptContext request, OperationOptions options) {

        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    public Object runScriptOnResource(ScriptContext request, OperationOptions options) {

        resource.runScript(request.getScriptLanguage(), request.getScriptText(), request.getScriptArguments());

        return null;
    }

    /**
     * {@inheritDoc}
     */
    public FilterTranslator<Filter> createFilterTranslator(ObjectClass objectClass, OperationOptions options) {
        log.info("createFilterTranslator::begin");
        validate(objectClass);

        log.info("createFilterTranslator::end");
        return new DummyFilterTranslator() {
        };
    }

    /**
     * {@inheritDoc}
     */
    public void executeQuery(ObjectClass objectClass, Filter query, ResultsHandler handler,
            OperationOptions options) {
        log.info("executeQuery({0},{1},{2},{3})", objectClass, query, handler, options);
        validate(objectClass);
        notNull(handler, "Results handled object can't be null.");

        Collection<String> attributesToGet = getAttrsToGet(options);
        log.ok("attributesToGet={0}", attributesToGet);

        try {
            if (ObjectClass.ACCOUNT.is(objectClass.getObjectClassValue())) {

                Collection<DummyAccount> accounts = resource.listAccounts();
                for (DummyAccount account : accounts) {
                    ConnectorObject co = convertToConnectorObject(account, attributesToGet);
                    if (matches(query, co)) {
                        co = filterOutAttributesToGet(co, attributesToGet);
                        handler.handle(co);
                    }
                }

            } else if (ObjectClass.GROUP.is(objectClass.getObjectClassValue())) {

                Collection<DummyGroup> groups = resource.listGroups();
                for (DummyGroup group : groups) {
                    ConnectorObject co = convertToConnectorObject(group, attributesToGet);
                    if (matches(query, co)) {
                        if (attributesToGetHasAttribute(attributesToGet, DummyGroup.ATTR_MEMBERS_NAME)) {
                            resource.recordGroupMembersReadCount();
                        }
                        co = filterOutAttributesToGet(co, attributesToGet);
                        handler.handle(co);
                    }
                }

            } else if (objectClass.is(OBJECTCLASS_PRIVILEGE_NAME)) {

                Collection<DummyPrivilege> privs = resource.listPrivileges();
                for (DummyPrivilege priv : privs) {
                    ConnectorObject co = convertToConnectorObject(priv, attributesToGet);
                    if (matches(query, co)) {
                        co = filterOutAttributesToGet(co, attributesToGet);
                        handler.handle(co);
                    }
                }

            } else {
                throw new ConnectorException("Unknown object class " + objectClass);
            }

        } catch (ConnectException e) {
            log.info("executeQuery::exception " + e);
            throw new ConnectionFailedException(e.getMessage(), e);
        } catch (FileNotFoundException e) {
            log.info("executeQuery::exception " + e);
            throw new ConnectorIOException(e.getMessage(), e);
        }

        log.info("executeQuery::end");
    }

    private boolean matches(Filter query, ConnectorObject co) {
        if (query == null) {
            return true;
        }
        if (configuration.getCaseIgnoreValues() || configuration.getCaseIgnoreId()) {
            return normalize(query).accept(normalize(co));
        }
        return query.accept(co);
    }

    private ConnectorObject normalize(ConnectorObject co) {
        ConnectorObjectBuilder cob = new ConnectorObjectBuilder();
        if (configuration.getCaseIgnoreId()) {
            cob.setUid(co.getUid().getUidValue().toLowerCase());
            cob.setName(co.getName().getName().toLowerCase());
        } else {
            cob.setUid(co.getUid());
            cob.setName(co.getName());
        }
        cob.setObjectClass(co.getObjectClass());
        for (Attribute attr : co.getAttributes()) {
            cob.addAttribute(normalize(attr));
        }
        return cob.build();
    }

    private Filter normalize(Filter filter) {
        if (filter instanceof ContainsFilter) {
            AttributeFilter afilter = (AttributeFilter) filter;
            return new ContainsFilter(normalize(afilter.getAttribute()));
        } else if (filter instanceof EndsWithFilter) {
            AttributeFilter afilter = (AttributeFilter) filter;
            return new EndsWithFilter(normalize(afilter.getAttribute()));
        } else if (filter instanceof EqualsFilter) {
            AttributeFilter afilter = (AttributeFilter) filter;
            return new EqualsFilter(normalize(afilter.getAttribute()));
        } else if (filter instanceof GreaterThanFilter) {
            AttributeFilter afilter = (AttributeFilter) filter;
            return new GreaterThanFilter(normalize(afilter.getAttribute()));
        } else if (filter instanceof GreaterThanOrEqualFilter) {
            AttributeFilter afilter = (AttributeFilter) filter;
            return new GreaterThanOrEqualFilter(normalize(afilter.getAttribute()));
        } else if (filter instanceof LessThanFilter) {
            AttributeFilter afilter = (AttributeFilter) filter;
            return new LessThanFilter(normalize(afilter.getAttribute()));
        } else if (filter instanceof LessThanOrEqualFilter) {
            AttributeFilter afilter = (AttributeFilter) filter;
            return new LessThanOrEqualFilter(normalize(afilter.getAttribute()));
        } else if (filter instanceof StartsWithFilter) {
            AttributeFilter afilter = (AttributeFilter) filter;
            return new StartsWithFilter(normalize(afilter.getAttribute()));
        } else if (filter instanceof ContainsAllValuesFilter) {
            AttributeFilter afilter = (AttributeFilter) filter;
            return new ContainsAllValuesFilter(normalize(afilter.getAttribute()));
        } else if (filter instanceof NotFilter) {
            NotFilter notFilter = (NotFilter) filter;
            return new NotFilter(normalize(notFilter.getFilter()));
        } else if (filter instanceof AndFilter) {
            AndFilter andFilter = (AndFilter) filter;
            return new AndFilter(normalize(andFilter.getLeft()), normalize(andFilter.getRight()));
        } else if (filter instanceof OrFilter) {
            OrFilter orFilter = (OrFilter) filter;
            return new OrFilter(normalize(orFilter.getLeft()), normalize(orFilter.getRight()));
        } else {
            return filter;
        }
    }

    private Attribute normalize(Attribute attr) {
        if (configuration.getCaseIgnoreValues()) {
            AttributeBuilder ab = new AttributeBuilder();
            ab.setName(attr.getName());
            for (Object value : attr.getValue()) {
                if (value instanceof String) {
                    ab.addValue(((String) value).toLowerCase());
                } else {
                    ab.addValue(value);
                }
            }
            return ab.build();
        } else {
            return attr;
        }
    }

    private ConnectorObject filterOutAttributesToGet(ConnectorObject co, Collection<String> attributesToGet) {
        if (attributesToGet == null) {
            return co;
        }
        ConnectorObjectBuilder cob = new ConnectorObjectBuilder();
        cob.setUid(co.getUid());
        cob.setName(co.getName());
        cob.setObjectClass(co.getObjectClass());
        Set<Attribute> attrs = new HashSet<Attribute>(co.getAttributes().size());
        for (Attribute attr : co.getAttributes()) {
            if (containsAttribute(attributesToGet, attr.getName())) {
                cob.addAttribute(attr);
            }
        }
        cob.addAttributes(attrs);
        return cob.build();
    }

    private boolean containsAttribute(Collection<String> attrs, String attrName) {
        for (String attr : attrs) {
            if (StringUtils.equalsIgnoreCase(attr, attrName)) {
                return true;
            }
        }
        return false;
    }

    /**
      * {@inheritDoc}
      */
    public void sync(ObjectClass objectClass, SyncToken token, SyncResultsHandler handler,
            final OperationOptions options) {
        log.info("sync::begin");
        validate(objectClass);

        Collection<String> attributesToGet = getAttrsToGet(options);

        try {
            int syncToken = (Integer) token.getValue();
            List<DummyDelta> deltas = resource.getDeltasSince(syncToken);
            for (DummyDelta delta : deltas) {

                Class<? extends DummyObject> deltaObjectClass = delta.getObjectClass();
                if (objectClass.is(ObjectClass.ALL_NAME)) {
                    // take all changes
                } else if (objectClass.is(ObjectClass.ACCOUNT_NAME)) {
                    if (deltaObjectClass != DummyAccount.class) {
                        log.ok("Skipping delta {0} because of objectclass mismatch", delta);
                        continue;
                    }
                } else if (objectClass.is(ObjectClass.GROUP_NAME)) {
                    if (deltaObjectClass != DummyGroup.class) {
                        log.ok("Skipping delta {0} because of objectclass mismatch", delta);
                        continue;
                    }
                }

                SyncDeltaBuilder deltaBuilder = new SyncDeltaBuilder();
                if (deltaObjectClass == DummyAccount.class) {
                    deltaBuilder.setObjectClass(ObjectClass.ACCOUNT);
                } else if (deltaObjectClass == DummyGroup.class) {
                    deltaBuilder.setObjectClass(ObjectClass.GROUP);
                } else if (deltaObjectClass == DummyPrivilege.class) {
                    deltaBuilder.setObjectClass(new ObjectClass(OBJECTCLASS_PRIVILEGE_NAME));
                } else {
                    throw new IllegalArgumentException("Unknown delta objectClass " + deltaObjectClass);
                }

                SyncDeltaType deltaType;
                if (delta.getType() == DummyDeltaType.ADD || delta.getType() == DummyDeltaType.MODIFY) {
                    if (resource.getSyncStyle() == DummySyncStyle.DUMB) {
                        deltaType = SyncDeltaType.CREATE_OR_UPDATE;
                    } else {
                        if (delta.getType() == DummyDeltaType.ADD) {
                            deltaType = SyncDeltaType.CREATE;
                        } else {
                            deltaType = SyncDeltaType.UPDATE;
                        }
                    }
                    if (deltaObjectClass == DummyAccount.class) {
                        DummyAccount account = resource.getAccountById(delta.getObjectId());
                        if (account == null) {
                            throw new IllegalStateException("We have delta for account '" + delta.getObjectId()
                                    + "' but such account does not exist");
                        }
                        ConnectorObject cobject = convertToConnectorObject(account, attributesToGet);
                        deltaBuilder.setObject(cobject);
                    } else if (deltaObjectClass == DummyGroup.class) {
                        DummyGroup group = resource.getGroupById(delta.getObjectId());
                        if (group == null) {
                            throw new IllegalStateException("We have delta for group '" + delta.getObjectId()
                                    + "' but such group does not exist");
                        }
                        ConnectorObject cobject = convertToConnectorObject(group, attributesToGet);
                        deltaBuilder.setObject(cobject);
                    } else if (deltaObjectClass == DummyPrivilege.class) {
                        DummyPrivilege privilege = resource.getPrivilegeById(delta.getObjectId());
                        if (privilege == null) {
                            throw new IllegalStateException("We have privilege for group '" + delta.getObjectId()
                                    + "' but such privilege does not exist");
                        }
                        ConnectorObject cobject = convertToConnectorObject(privilege, attributesToGet);
                        deltaBuilder.setObject(cobject);
                    } else {
                        throw new IllegalArgumentException("Unknown delta objectClass " + deltaObjectClass);
                    }
                } else if (delta.getType() == DummyDeltaType.DELETE) {
                    deltaType = SyncDeltaType.DELETE;
                } else {
                    throw new IllegalStateException("Unknown delta type " + delta.getType());
                }
                deltaBuilder.setDeltaType(deltaType);

                deltaBuilder.setToken(new SyncToken(delta.getSyncToken()));

                Uid uid;
                if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_NAME)) {
                    uid = new Uid(delta.getObjectName());
                } else if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_UUID)) {
                    uid = new Uid(delta.getObjectId());
                } else {
                    throw new IllegalStateException("Unknown UID mode " + configuration.getUidMode());
                }
                deltaBuilder.setUid(uid);

                SyncDelta syncDelta = deltaBuilder.build();
                log.info("sync::handle {0}", syncDelta);
                handler.handle(syncDelta);
            }

        } catch (ConnectException e) {
            log.info("sync::exception " + e);
            throw new ConnectionFailedException(e.getMessage(), e);
        } catch (FileNotFoundException e) {
            log.info("sync::exception " + e);
            throw new ConnectorIOException(e.getMessage(), e);
        }

        log.info("sync::end");
    }

    private Collection<String> getAttrsToGet(OperationOptions options) {
        Collection<String> attributesToGet = null;
        if (options != null) {
            String[] attributesToGetArray = options.getAttributesToGet();
            if (attributesToGetArray != null && attributesToGetArray.length != 0) {
                attributesToGet = Arrays.asList(attributesToGetArray);
            }
        }
        return attributesToGet;
    }

    /**
      * {@inheritDoc}
      */
    public SyncToken getLatestSyncToken(ObjectClass objectClass) {
        log.info("getLatestSyncToken::begin");
        validate(objectClass);
        int latestSyncToken = resource.getLatestSyncToken();
        log.info("getLatestSyncToken::end, returning token {0}.", latestSyncToken);
        return new SyncToken(latestSyncToken);
    }

    /**
     * {@inheritDoc}
     */
    public void test() {
        log.info("test::begin");
        log.info("Validating configuration.");
        configuration.validate();

        // Produce log messages on all levels. The tests may check if they are really logged.
        log.error(LOG_MARKER + " DummyConnectorIcfError");
        log.info(LOG_MARKER + " DummyConnectorIcfInfo");
        log.warn(LOG_MARKER + " DummyConnectorIcfWarn");
        log.ok(LOG_MARKER + " DummyConnectorIcfOk");

        log.info("Dummy Connector JUL logger as seen by the connector: " + julLogger + "; classloader "
                + julLogger.getClass().getClassLoader());

        // Same thing using JUL
        julLogger.severe(LOG_MARKER + " DummyConnectorJULsevere");
        julLogger.warning(LOG_MARKER + " DummyConnectorJULwarning");
        julLogger.info(LOG_MARKER + " DummyConnectorJULinfo");
        julLogger.fine(LOG_MARKER + " DummyConnectorJULfine");
        julLogger.finer(LOG_MARKER + " DummyConnectorJULfiner");
        julLogger.finest(LOG_MARKER + " DummyConnectorJULfinest");

        log.info("Test configuration was successful.");
        log.info("test::end");
    }

    private ConnectorObjectBuilder createConnectorObjectBuilderCommon(DummyObject dummyObject,
            DummyObjectClass objectClass, Collection<String> attributesToGet, boolean supportActivation) {
        ConnectorObjectBuilder builder = new ConnectorObjectBuilder();

        if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_NAME)) {
            builder.setUid(dummyObject.getName());
        } else if (configuration.getUidMode().equals(DummyConfiguration.UID_MODE_UUID)) {
            builder.setUid(dummyObject.getId());
        } else {
            throw new IllegalStateException("Unknown UID mode " + configuration.getUidMode());
        }

        builder.addAttribute(Name.NAME, dummyObject.getName());

        for (String name : dummyObject.getAttributeNames()) {
            DummyAttributeDefinition attrDef = objectClass.getAttributeDefinition(name);
            if (attrDef == null) {
                throw new IllegalArgumentException("Unknown account attribute '" + name + "'");
            }
            if (!attrDef.isReturnedByDefault()) {
                if (attributesToGet != null && !attributesToGet.contains(name)) {
                    continue;
                }
            }
            // Return all attributes that are returned by default. We will filter them out later.
            Set<Object> values = dummyObject.getAttributeValues(name, Object.class);
            if (configuration.isVaryLetterCase()) {
                name = varyLetterCase(name);
            }
            if (values != null && !values.isEmpty()) {
                builder.addAttribute(name, values);
            }
        }

        if (supportActivation) {
            if (attributesToGet == null || attributesToGet.contains(OperationalAttributes.ENABLE_NAME)) {
                builder.addAttribute(OperationalAttributes.ENABLE_NAME, dummyObject.isEnabled());
            }

            if (dummyObject.getValidFrom() != null && (attributesToGet == null
                    || attributesToGet.contains(OperationalAttributes.ENABLE_DATE_NAME))) {
                builder.addAttribute(OperationalAttributes.ENABLE_DATE_NAME,
                        convertToLong(dummyObject.getValidFrom()));
            }

            if (dummyObject.getValidTo() != null && (attributesToGet == null
                    || attributesToGet.contains(OperationalAttributes.DISABLE_DATE_NAME))) {
                builder.addAttribute(OperationalAttributes.DISABLE_DATE_NAME,
                        convertToLong(dummyObject.getValidTo()));
            }
        }

        return builder;
    }

    private String varyLetterCase(String name) {
        StringBuilder sb = new StringBuilder(name.length());
        for (char c : name.toCharArray()) {
            double a = Math.random();
            if (a < 0.4) {
                c = Character.toLowerCase(c);
            } else if (a > 0.7) {
                c = Character.toUpperCase(c);
            }
            sb.append(c);
        }
        return sb.toString();
    }

    private Long convertToLong(Date date) {
        if (date == null) {
            return null;
        }
        return date.getTime();
    }

    private ConnectorObject convertToConnectorObject(DummyAccount account, Collection<String> attributesToGet) {

        DummyObjectClass objectClass;
        try {
            objectClass = resource.getAccountObjectClass();
        } catch (ConnectException e) {
            log.error(e, e.getMessage());
            throw new ConnectionFailedException(e.getMessage(), e);
        } catch (FileNotFoundException e) {
            log.error(e, e.getMessage());
            throw new ConnectorIOException(e.getMessage(), e);
        }

        ConnectorObjectBuilder builder = createConnectorObjectBuilderCommon(account, objectClass, attributesToGet,
                true);
        builder.setObjectClass(ObjectClass.ACCOUNT);

        // Password is not returned by default (hardcoded ICF specification)
        if (account.getPassword() != null && configuration.getReadablePassword() && attributesToGet != null
                && attributesToGet.contains(OperationalAttributes.PASSWORD_NAME)) {
            GuardedString gs = new GuardedString(account.getPassword().toCharArray());
            builder.addAttribute(OperationalAttributes.PASSWORD_NAME, gs);
        }

        if (account.isLockout() != null) {
            builder.addAttribute(OperationalAttributes.LOCK_OUT_NAME, account.isLockout());
        }

        return builder.build();
    }

    private ConnectorObject convertToConnectorObject(DummyGroup group, Collection<String> attributesToGet) {
        ConnectorObjectBuilder builder = createConnectorObjectBuilderCommon(group, resource.getGroupObjectClass(),
                attributesToGet, true);
        builder.setObjectClass(ObjectClass.GROUP);
        return builder.build();
    }

    private ConnectorObject convertToConnectorObject(DummyPrivilege priv, Collection<String> attributesToGet) {
        ConnectorObjectBuilder builder = createConnectorObjectBuilderCommon(priv,
                resource.getPrivilegeObjectClass(), attributesToGet, false);
        builder.setObjectClass(new ObjectClass(OBJECTCLASS_PRIVILEGE_NAME));
        return builder.build();
    }

    private DummyAccount convertToAccount(Set<Attribute> createAttributes)
            throws ConnectException, FileNotFoundException {
        log.ok("Create attributes: {0}", createAttributes);
        String userName = Utils.getMandatoryStringAttribute(createAttributes, Name.NAME);
        if (configuration.getUpCaseName()) {
            userName = StringUtils.upperCase(userName);
        }
        log.ok("Username {0}", userName);
        final DummyAccount newAccount = new DummyAccount(userName);

        Boolean enabled = null;
        for (Attribute attr : createAttributes) {
            if (attr.is(Uid.NAME)) {
                throw new IllegalArgumentException("UID explicitly specified in the account attributes");

            } else if (attr.is(Name.NAME)) {
                // Skip, already processed

            } else if (attr.is(OperationalAttributeInfos.PASSWORD.getName())) {
                changePassword(newAccount, attr);

            } else if (attr.is(OperationalAttributeInfos.ENABLE.getName())) {
                enabled = getBoolean(attr);
                newAccount.setEnabled(enabled);

            } else if (attr.is(OperationalAttributeInfos.ENABLE_DATE.getName())) {
                if (configuration.getSupportValidity()) {
                    newAccount.setValidFrom(getDate(attr));
                } else {
                    throw new IllegalArgumentException(
                            "ENABLE_DATE specified in the account attributes while not supporting it");
                }

            } else if (attr.is(OperationalAttributeInfos.DISABLE_DATE.getName())) {
                if (configuration.getSupportValidity()) {
                    newAccount.setValidTo(getDate(attr));
                } else {
                    throw new IllegalArgumentException(
                            "DISABLE_DATE specified in the account attributes while not supporting it");
                }

            } else if (attr.is(OperationalAttributeInfos.LOCK_OUT.getName())) {
                Boolean lockout = getBoolean(attr);
                newAccount.setLockout(lockout);

            } else {
                String name = attr.getName();
                try {
                    newAccount.replaceAttributeValues(name, attr.getValue());
                } catch (SchemaViolationException e) {
                    // we cannot throw checked exceptions. But this one looks suitable.
                    // Note: let's do the bad thing and add exception loaded by this classloader as inner exception here
                    // The framework should deal with it ... somehow
                    throw new IllegalArgumentException(e.getMessage(), e);
                }
            }
        }

        if (configuration.getRequireExplicitEnable() && enabled == null) {
            throw new IllegalArgumentException(
                    "Explicit value for ENABLE attribute was not provided and the connector is set to require it");
        }

        return newAccount;
    }

    private DummyGroup convertToGroup(Set<Attribute> createAttributes)
            throws ConnectException, FileNotFoundException {
        String icfName = Utils.getMandatoryStringAttribute(createAttributes, Name.NAME);
        if (configuration.getUpCaseName()) {
            icfName = StringUtils.upperCase(icfName);
        }
        final DummyGroup newGroup = new DummyGroup(icfName);

        Boolean enabled = null;
        for (Attribute attr : createAttributes) {
            if (attr.is(Uid.NAME)) {
                throw new IllegalArgumentException("UID explicitly specified in the group attributes");

            } else if (attr.is(Name.NAME)) {
                // Skip, already processed

            } else if (attr.is(OperationalAttributeInfos.PASSWORD.getName())) {
                throw new IllegalArgumentException("Password specified for a group");

            } else if (attr.is(OperationalAttributeInfos.ENABLE.getName())) {
                enabled = getBoolean(attr);
                newGroup.setEnabled(enabled);

            } else if (attr.is(OperationalAttributeInfos.ENABLE_DATE.getName())) {
                if (configuration.getSupportValidity()) {
                    newGroup.setValidFrom(getDate(attr));
                } else {
                    throw new IllegalArgumentException(
                            "ENABLE_DATE specified in the group attributes while not supporting it");
                }

            } else if (attr.is(OperationalAttributeInfos.DISABLE_DATE.getName())) {
                if (configuration.getSupportValidity()) {
                    newGroup.setValidTo(getDate(attr));
                } else {
                    throw new IllegalArgumentException(
                            "DISABLE_DATE specified in the group attributes while not supporting it");
                }

            } else {
                String name = attr.getName();
                try {
                    newGroup.replaceAttributeValues(name, attr.getValue());
                } catch (SchemaViolationException e) {
                    throw new IllegalArgumentException(e.getMessage(), e);
                }
            }
        }

        return newGroup;
    }

    private DummyPrivilege convertToPriv(Set<Attribute> createAttributes)
            throws ConnectException, FileNotFoundException {
        String icfName = Utils.getMandatoryStringAttribute(createAttributes, Name.NAME);
        if (configuration.getUpCaseName()) {
            icfName = StringUtils.upperCase(icfName);
        }
        final DummyPrivilege newPriv = new DummyPrivilege(icfName);

        Boolean enabled = null;
        for (Attribute attr : createAttributes) {
            if (attr.is(Uid.NAME)) {
                throw new IllegalArgumentException("UID explicitly specified in the group attributes");

            } else if (attr.is(Name.NAME)) {
                // Skip, already processed

            } else if (attr.is(OperationalAttributeInfos.PASSWORD.getName())) {
                throw new IllegalArgumentException("Password specified for a privilege");

            } else if (attr.is(OperationalAttributeInfos.ENABLE.getName())) {
                throw new IllegalArgumentException("Unsupported ENABLE attribute in privilege");

            } else {
                String name = attr.getName();
                try {
                    newPriv.replaceAttributeValues(name, attr.getValue());
                } catch (SchemaViolationException e) {
                    throw new IllegalArgumentException(e.getMessage(), e);
                }
            }
        }

        return newPriv;
    }

    private boolean getBoolean(Attribute attr) {
        if (attr.getValue() == null || attr.getValue().isEmpty()) {
            throw new IllegalArgumentException("Empty " + attr.getName() + " attribute was provided");
        }
        Object object = attr.getValue().get(0);
        if (!(object instanceof Boolean)) {
            throw new IllegalArgumentException("Attribute " + attr.getName() + " was provided as "
                    + object.getClass().getName() + " while expecting boolean");
        }
        return ((Boolean) object).booleanValue();
    }

    private Date getDate(Attribute attr) {
        if (attr.getValue() == null || attr.getValue().isEmpty()) {
            throw new IllegalArgumentException("Empty date attribute was provided");
        }
        Object object = attr.getValue().get(0);

        if (object == null) {
            return null;
        }

        if (!(object instanceof Long)) {
            throw new IllegalArgumentException(
                    "Date attribute was provided as " + object.getClass().getName() + " while expecting long");
        }
        return new Date(((Long) object).longValue());
    }

    private void changePassword(final DummyAccount account, Attribute attr) {
        if (attr.getValue() == null || attr.getValue().isEmpty()) {
            throw new IllegalArgumentException("Empty password was provided");
        }
        Object passwdObject = attr.getValue().get(0);
        if (!(passwdObject instanceof GuardedString)) {
            throw new IllegalArgumentException("Password was provided as " + passwdObject.getClass().getName()
                    + " while expecting GuardedString");
        }
        ((GuardedString) passwdObject).access(new Accessor() {
            @Override
            public void access(char[] passwdChars) {
                account.setPassword(new String(passwdChars));
            }
        });
    }

    private boolean attributesToGetHasAttribute(Collection<String> attributesToGet, String attrName) {
        if (attributesToGet == null) {
            return true;
        }
        return attributesToGet.contains(attrName);
    }

}