com.evolveum.polygon.connector.ldap.schema.AbstractSchemaTranslator.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.polygon.connector.ldap.schema.AbstractSchemaTranslator.java

Source

/**
 * Copyright (c) 2014-2016 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.polygon.connector.ldap.schema;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.naming.directory.SchemaViolationException;

import org.apache.commons.lang.ArrayUtils;
import org.apache.directory.api.ldap.extras.controls.vlv.VirtualListViewRequest;
import org.apache.directory.api.ldap.model.constants.SchemaConstants;
import org.apache.directory.api.ldap.model.entry.BinaryValue;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.entry.StringValue;
import org.apache.directory.api.ldap.model.entry.Value;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
import org.apache.directory.api.ldap.model.message.controls.PagedResults;
import org.apache.directory.api.ldap.model.message.controls.SortRequest;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.schema.AttributeType;
import org.apache.directory.api.ldap.model.schema.LdapSyntax;
import org.apache.directory.api.ldap.model.schema.MutableAttributeType;
import org.apache.directory.api.ldap.model.schema.SchemaManager;
import org.apache.directory.api.util.GeneralizedTime;
import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.apache.directory.ldap.client.api.exception.InvalidConnectionException;
import org.identityconnectors.common.Base64;
import org.identityconnectors.common.logging.Log;
import org.identityconnectors.common.security.GuardedString;
import org.identityconnectors.framework.common.exceptions.ConfigurationException;
import org.identityconnectors.framework.common.exceptions.ConnectorException;
import org.identityconnectors.framework.common.exceptions.InvalidAttributeValueException;
import org.identityconnectors.framework.common.objects.Attribute;
import org.identityconnectors.framework.common.objects.AttributeBuilder;
import org.identityconnectors.framework.common.objects.AttributeInfo;
import org.identityconnectors.framework.common.objects.AttributeInfoBuilder;
import org.identityconnectors.framework.common.objects.AttributeValueCompleteness;
import org.identityconnectors.framework.common.objects.ConnectorObject;
import org.identityconnectors.framework.common.objects.ConnectorObjectBuilder;
import org.identityconnectors.framework.common.objects.Name;
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.OperationOptionInfoBuilder;
import org.identityconnectors.framework.common.objects.OperationalAttributeInfos;
import org.identityconnectors.framework.common.objects.PredefinedAttributeInfos;
import org.identityconnectors.framework.common.objects.PredefinedAttributes;
import org.identityconnectors.framework.common.objects.Schema;
import org.identityconnectors.framework.common.objects.SchemaBuilder;
import org.identityconnectors.framework.common.objects.Uid;
import org.identityconnectors.framework.spi.operations.SearchOp;
import org.identityconnectors.framework.spi.operations.SyncOp;

import com.evolveum.polygon.common.SchemaUtil;
import com.evolveum.polygon.connector.ldap.AbstractLdapConfiguration;
import com.evolveum.polygon.connector.ldap.ConnectionManager;
import com.evolveum.polygon.connector.ldap.LdapConfiguration;
import com.evolveum.polygon.connector.ldap.LdapConnector;
import com.evolveum.polygon.connector.ldap.LdapConstants;
import com.evolveum.polygon.connector.ldap.LdapUtil;

/**
 * @author semancik
 *
 */
public abstract class AbstractSchemaTranslator<C extends AbstractLdapConfiguration> {

    private static final Log LOG = Log.getLog(AbstractSchemaTranslator.class);
    private static final Collection<String> STRING_ATTRIBUTE_NAMES = new ArrayList<>();
    private static final Map<String, TypeSubType> SYNTAX_MAP = new HashMap<>();

    private SchemaManager schemaManager;
    private C configuration;
    private Schema icfSchema = null;

    public AbstractSchemaTranslator(SchemaManager schemaManager, C configuration) {
        super();
        this.schemaManager = schemaManager;
        this.configuration = configuration;
    }

    public Schema getIcfSchema() {
        return icfSchema;
    }

    public SchemaManager getSchemaManager() {
        return schemaManager;
    }

    public C getConfiguration() {
        return configuration;
    }

    @SuppressWarnings("unchecked")
    public Schema translateSchema(ConnectionManager<C> connection) throws InvalidConnectionException {
        SchemaBuilder schemaBuilder = new SchemaBuilder(LdapConnector.class);
        LOG.ok("Translating LDAP schema from {0}", schemaManager);

        for (org.apache.directory.api.ldap.model.schema.ObjectClass ldapObjectClass : schemaManager
                .getObjectClassRegistry()) {
            if (shouldTranslateObjectClass(ldapObjectClass.getName())) {
                LOG.ok("Found LDAP schema object class {0}, translating", ldapObjectClass.getName());
                ObjectClassInfoBuilder ocib = new ObjectClassInfoBuilder();
                ocib.setType(toIcfObjectClassType(ldapObjectClass));
                Map<String, AttributeInfo> attrInfoList = new HashMap<>();
                addAttributeTypes(attrInfoList, ldapObjectClass);
                ocib.addAllAttributeInfo(attrInfoList.values());

                if (ldapObjectClass.isAuxiliary()) {
                    ocib.setAuxiliary(true);
                }

                extendObjectClassDefinition(ocib, ldapObjectClass);
                schemaBuilder.defineObjectClass(ocib.build());
            } else {
                LOG.ok("Found LDAP schema object class {0}, skipping", ldapObjectClass.getName());
            }
        }

        schemaBuilder.defineOperationOption(OperationOptionInfoBuilder.buildAttributesToGet(), SearchOp.class,
                SyncOp.class);
        schemaBuilder.defineOperationOption(OperationOptionInfoBuilder.buildReturnDefaultAttributes(),
                SearchOp.class, SyncOp.class);
        schemaBuilder.defineOperationOption(OperationOptionInfoBuilder.buildAllowPartialResults(), SearchOp.class);
        schemaBuilder.defineOperationOption(OperationOptionInfoBuilder.buildContainer(), SearchOp.class);
        schemaBuilder.defineOperationOption(OperationOptionInfoBuilder.buildScope(), SearchOp.class);

        List<String> supportedControls;
        try {
            supportedControls = connection.getDefaultConnection().getSupportedControls();
        } catch (InvalidConnectionException e) {
            throw e;
        } catch (LdapException e) {
            if (e.getCause() instanceof InvalidConnectionException) {
                throw (InvalidConnectionException) e.getCause();
            }
            throw LdapUtil.processLdapException("Error getting supported controls", e);
        }
        if (supportedControls.contains(PagedResults.OID)
                || supportedControls.contains(VirtualListViewRequest.OID)) {
            schemaBuilder.defineOperationOption(OperationOptionInfoBuilder.buildPageSize(), SearchOp.class);
            schemaBuilder.defineOperationOption(OperationOptionInfoBuilder.buildPagedResultsCookie(),
                    SearchOp.class);
        }
        if (supportedControls.contains(VirtualListViewRequest.OID)) {
            schemaBuilder.defineOperationOption(OperationOptionInfoBuilder.buildPagedResultsOffset(),
                    SearchOp.class);
        }
        if (supportedControls.contains(SortRequest.OID)) {
            schemaBuilder.defineOperationOption(OperationOptionInfoBuilder.buildSortKeys(), SearchOp.class);
        }

        icfSchema = schemaBuilder.build();
        LOG.ok("Translated schema {0}", icfSchema);
        return icfSchema;
    }

    protected void extendObjectClassDefinition(ObjectClassInfoBuilder ocib,
            org.apache.directory.api.ldap.model.schema.ObjectClass ldapObjectClass) {
        // Nothing to do. Expected to be overridden in subclasses.
    }

    protected boolean shouldTranslateObjectClass(String ldapObjectClassName) {
        return true;
    }

    protected String toIcfObjectClassType(org.apache.directory.api.ldap.model.schema.ObjectClass ldapObjectClass) {
        return ldapObjectClass.getName();
    }

    protected String toLdapObjectClassName(ObjectClass icfObjectClass) {
        return icfObjectClass.getObjectClassValue();
    }

    /**
     * Make sure that we have icfSchema  
     */
    public void prepareIcfSchema(ConnectionManager<C> connectionManager) throws InvalidConnectionException {
        if (icfSchema == null) {
            translateSchema(connectionManager);
        }
    }

    private void addAttributeTypes(Map<String, AttributeInfo> attrInfoList,
            org.apache.directory.api.ldap.model.schema.ObjectClass ldapObjectClass) {

        // ICF UID
        String uidAttribudeLdapName = configuration.getUidAttribute();
        AttributeInfoBuilder uidAib = new AttributeInfoBuilder(Uid.NAME);
        uidAib.setNativeName(uidAttribudeLdapName);
        uidAib.setRequired(false); // Must be optional. It is not present for create operations
        AttributeType uidAttributeLdapType = null;

        try {
            uidAttributeLdapType = schemaManager.lookupAttributeTypeRegistry(uidAttribudeLdapName);
        } catch (LdapException e) {
            // We can live with this
            LOG.ok("Got exception looking up UID atribute {0}: {1} ({2}) (probabably harmless)",
                    uidAttribudeLdapName, e.getMessage(), e.getClass());
        }

        // UID must be string. It is hardcoded in the framework.
        uidAib.setType(String.class);

        if (uidAttributeLdapType != null) {
            uidAib.setSubtype(toIcfSubtype(String.class, uidAttributeLdapType, Uid.NAME));
            setAttributeMultiplicityAndPermissions(uidAttributeLdapType, Uid.NAME, uidAib);
        } else {
            uidAib.setCreateable(false);
            uidAib.setUpdateable(false);
            uidAib.setReadable(true);
        }

        AttributeInfo attributeInfo = uidAib.build();
        attrInfoList.put(attributeInfo.getName(), attributeInfo);

        // ICF NAME
        AttributeInfoBuilder nameAib = new AttributeInfoBuilder(Name.NAME);
        nameAib.setType(String.class);
        nameAib.setNativeName(LdapConfiguration.PSEUDO_ATTRIBUTE_DN_NAME);
        nameAib.setSubtype(AttributeInfo.Subtypes.STRING_LDAP_DN);
        nameAib.setRequired(true);
        attributeInfo = nameAib.build();
        attrInfoList.put(attributeInfo.getName(), attributeInfo);

        // AUXILIARY_OBJECT_CLASS
        attrInfoList.put(PredefinedAttributeInfos.AUXILIARY_OBJECT_CLASS.getName(),
                PredefinedAttributeInfos.AUXILIARY_OBJECT_CLASS);

        addAttributeTypesFromLdapSchema(attrInfoList, ldapObjectClass);
        addExtraOperationalAttributes(attrInfoList);
    }

    private void addExtraOperationalAttributes(Map<String, AttributeInfo> attrInfoList) {
        for (String operationalAttributeLdapName : configuration.getOperationalAttributes()) {
            if (containsAttribute(attrInfoList, operationalAttributeLdapName)) {
                continue;
            }
            AttributeInfoBuilder aib = new AttributeInfoBuilder(operationalAttributeLdapName);
            aib.setRequired(false);
            aib.setNativeName(operationalAttributeLdapName);

            AttributeType attributeType = null;
            try {
                attributeType = schemaManager.lookupAttributeTypeRegistry(operationalAttributeLdapName);
            } catch (LdapException e) {
                // Ignore. We want this attribute even if it is not in the LDAP schema
            }

            if (attributeType != null) {
                LdapSyntax ldapSyntax = getSyntax(attributeType);
                Class<?> icfType = toIcfType(ldapSyntax, operationalAttributeLdapName);
                aib.setType(icfType);
                aib.setSubtype(toIcfSubtype(icfType, attributeType, operationalAttributeLdapName));
                LOG.ok("Translating {0} -> {1} ({2} -> {3}) (operational)", operationalAttributeLdapName,
                        operationalAttributeLdapName, ldapSyntax == null ? null : ldapSyntax.getOid(), icfType);
                setAttributeMultiplicityAndPermissions(attributeType, operationalAttributeLdapName, aib);
            } else {
                LOG.ok("Translating {0} -> {1} ({2} -> {3}) (operational, not defined in schema)",
                        operationalAttributeLdapName, operationalAttributeLdapName, null, String.class);
                aib.setType(String.class);
                aib.setMultiValued(false);
            }
            aib.setReturnedByDefault(false);

            AttributeInfo attributeInfo = aib.build();
            attrInfoList.put(attributeInfo.getName(), attributeInfo);
        }

    }

    private void addAttributeTypesFromLdapSchema(Map<String, AttributeInfo> attrInfoList,
            org.apache.directory.api.ldap.model.schema.ObjectClass ldapObjectClass) {
        LOG.ok("  ... translating attributes from {0}:\n{1}\nMUST\n{2}", ldapObjectClass.getName(), ldapObjectClass,
                ldapObjectClass.getMustAttributeTypes());
        addAttributeTypes(attrInfoList, ldapObjectClass.getMustAttributeTypes(), true);
        LOG.ok("  ... translating attributes from {0}:\n{1}\nMAY\n{2}", ldapObjectClass.getName(), ldapObjectClass,
                ldapObjectClass.getMayAttributeTypes());
        addAttributeTypes(attrInfoList, ldapObjectClass.getMayAttributeTypes(), false);

        List<org.apache.directory.api.ldap.model.schema.ObjectClass> superiors = ldapObjectClass.getSuperiors();
        if ((superiors != null) && (superiors.size() > 0)) {
            for (org.apache.directory.api.ldap.model.schema.ObjectClass superior : superiors) {
                addAttributeTypesFromLdapSchema(attrInfoList, superior);
            }
        }
    }

    private void addAttributeTypes(Map<String, AttributeInfo> attrInfoList, List<AttributeType> attributeTypes,
            boolean isRequired) {
        for (AttributeType ldapAttribute : attributeTypes) {
            if (!shouldTranslateAttribute(ldapAttribute.getName())) {
                LOG.ok("Skipping translation of attribute {0} because it should not be translated",
                        ldapAttribute.getName());
                continue;
            }

            // Compare the name *or* the OID (the name may be null)
            if ((SchemaConstants.OBJECT_CLASS_AT.equalsIgnoreCase(ldapAttribute.getName()))
                    || SchemaConstants.OBJECT_CLASS_AT_OID.equals(ldapAttribute.getOid())) {
                continue;
            }
            if (ldapAttribute.getName().equalsIgnoreCase(getConfiguration().getUidAttribute())) {
                // This is handled separately as __UID__ attribute
                continue;
            }
            String icfAttributeName = toIcfAttributeName(ldapAttribute.getName());
            if (containsAttribute(attrInfoList, icfAttributeName)) {
                LOG.ok("Skipping translation of attribute {0} because it is already translated",
                        ldapAttribute.getName());
                continue;
            }
            AttributeInfoBuilder aib = new AttributeInfoBuilder(icfAttributeName);
            aib.setRequired(isRequired);

            LdapSyntax ldapSyntax = getSyntax(ldapAttribute);
            if (ldapSyntax == null) {
                LOG.warn("No syntax for attribute: {0}", ldapAttribute.getName());
            }

            Class<?> icfType = toIcfType(ldapSyntax, icfAttributeName);
            aib.setType(icfType);
            aib.setSubtype(toIcfSubtype(icfType, ldapAttribute, icfAttributeName));
            aib.setNativeName(ldapAttribute.getName());
            if (isOperational(ldapAttribute)) {
                aib.setReturnedByDefault(false);
            }
            setAttributeMultiplicityAndPermissions(ldapAttribute, icfAttributeName, aib);
            LOG.ok("Translating {0} -> {1} ({2} -> {3})", ldapAttribute.getName(), icfAttributeName,
                    ldapSyntax == null ? null : ldapSyntax.getOid(), icfType);
            AttributeInfo attributeInfo = aib.build();
            attrInfoList.put(attributeInfo.getName(), attributeInfo);
        }
    }

    protected boolean isOperational(AttributeType ldapAttribute) {
        return ldapAttribute.isOperational();
    }

    protected void setAttributeMultiplicityAndPermissions(AttributeType ldapAttributeType, String icfAttributeName,
            AttributeInfoBuilder aib) {
        if (ldapAttributeType.isSingleValued()) {
            aib.setMultiValued(false);
        } else {
            aib.setMultiValued(true);
        }
        if (OperationalAttributeInfos.PASSWORD.is(icfAttributeName)) {
            switch (configuration.getPasswordReadStrategy()) {
            case AbstractLdapConfiguration.PASSWORD_READ_STRATEGY_READABLE:
            case AbstractLdapConfiguration.PASSWORD_READ_STRATEGY_INCOMPLETE_READ:
                aib.setReadable(true);
                break;
            case AbstractLdapConfiguration.PASSWORD_READ_STRATEGY_UNREADABLE:
                aib.setReturnedByDefault(false);
                aib.setReadable(false);
                break;
            default:
                throw new ConfigurationException(
                        "Unknown passoword read strategy " + configuration.getPasswordReadStrategy());
            }
        } else {
            aib.setReadable(true);
        }
        if (ldapAttributeType.isReadOnly() || !ldapAttributeType.isUserModifiable()) {
            aib.setCreateable(false);
            aib.setUpdateable(false);
        } else {
            aib.setCreateable(true);
            aib.setUpdateable(true);
        }
    }

    private boolean containsAttribute(Map<String, AttributeInfo> attrInfoList, String icfAttributeName) {
        return attrInfoList.containsKey(icfAttributeName);
    }

    private String toIcfAttributeName(String ldapAttributeName) {
        if (ldapAttributeName.equalsIgnoreCase(configuration.getPasswordAttribute())) {
            return OperationalAttributeInfos.PASSWORD.getName();
        }
        return ldapAttributeName;
    }

    public org.apache.directory.api.ldap.model.schema.ObjectClass toLdapObjectClass(ObjectClass icfObjectClass) {
        String ldapObjectClassName = toLdapObjectClassName(icfObjectClass);
        try {
            return schemaManager.lookupObjectClassRegistry(ldapObjectClassName);
        } catch (LdapException e) {
            throw new IllegalArgumentException("Unknown object class " + icfObjectClass + ": " + e.getMessage(), e);
        }
    }

    /**
     * Throws exception if the attribute is illegal.
     * Return null if the attribute is legal, but we do not have any definition for it.
     */
    public AttributeType toLdapAttribute(org.apache.directory.api.ldap.model.schema.ObjectClass ldapObjectClass,
            String icfAttributeName) {
        if (Name.NAME.equals(icfAttributeName)) {
            return null;
        }
        String ldapAttributeName;
        if (Uid.NAME.equals(icfAttributeName)) {
            ldapAttributeName = configuration.getUidAttribute();
        } else if (OperationalAttributeInfos.PASSWORD.is(icfAttributeName)) {
            ldapAttributeName = configuration.getPasswordAttribute();
        } else {
            ldapAttributeName = icfAttributeName;
        }
        try {
            AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry(ldapAttributeName);
            if (attributeType == null && configuration.isAllowUnknownAttributes()) {
                // Create fake attribute type
                attributeType = createFauxAttributeType(ldapAttributeName);
            }
            return attributeType;
        } catch (LdapException e) {
            if (ArrayUtils.contains(configuration.getOperationalAttributes(), ldapAttributeName)
                    || configuration.isAllowUnknownAttributes()) {
                // Create fake attribute type
                AttributeType attributeType = new AttributeType(ldapAttributeName);
                attributeType.setNames(ldapAttributeName);
                return attributeType;
            } else {
                throw new IllegalArgumentException("Unknown LDAP attribute " + ldapAttributeName
                        + " (translated from ICF attribute " + icfAttributeName + ")", e);
            }
        }
    }

    public AttributeType createFauxAttributeType(String attributeName) {
        MutableAttributeType mutableLdapAttributeType = new MutableAttributeType(attributeName);
        mutableLdapAttributeType.setNames(attributeName);
        mutableLdapAttributeType.setSyntaxOid(SchemaConstants.DIRECTORY_STRING_SYNTAX);
        return mutableLdapAttributeType;
    }

    public Class<?> toIcfType(LdapSyntax syntax, String icfAttributeName) {
        if (OperationalAttributeInfos.PASSWORD.is(icfAttributeName)) {
            return GuardedString.class;
        }
        if (syntax == null) {
            // We may be in a quirks mode. Server schema may not be consistent (e.g. 389ds schema).
            // Therefore syntax may be null. Fall back to default in that case.
            return String.class;
        }
        Class<?> type = null;
        TypeSubType typeSubtype = SYNTAX_MAP.get(syntax.getName());

        if (typeSubtype != null) {
            type = typeSubtype.type;
            if (type == Date.class) {
                if (AbstractLdapConfiguration.TIMESTAMP_PRESENTATION_UNIX_EPOCH
                        .equals(getConfiguration().getTimestampPresentation())) {
                    type = long.class;
                } else {
                    type = String.class;
                }
            }
        }

        if (type == null) {
            LOG.warn("No type mapping for syntax {0}, using string", syntax.getName());
            return String.class;
        } else {
            return type;
        }
    }

    public String toIcfSubtype(Class<?> icfType, AttributeType ldapAttribute, String icfAttributeName) {
        if (OperationalAttributeInfos.PASSWORD.is(icfAttributeName)) {
            return null;
        }
        if (ldapAttribute == null) {
            return null;
        }
        if (hasEqualityMatching(ldapAttribute, SchemaConstants.CASE_IGNORE_MATCH_MR,
                SchemaConstants.CASE_IGNORE_MATCH_MR_OID)) {
            return AttributeInfo.Subtypes.STRING_CASE_IGNORE.toString();
        }
        if (hasEqualityMatching(ldapAttribute, SchemaConstants.CASE_IGNORE_IA5_MATCH_MR,
                SchemaConstants.CASE_IGNORE_IA5_MATCH_MR_OID)) {
            return AttributeInfo.Subtypes.STRING_CASE_IGNORE.toString();
        }
        if (hasEqualityMatching(ldapAttribute, SchemaConstants.UUID_MATCH_MR, SchemaConstants.UUID_MATCH_MR_OID)) {
            return AttributeInfo.Subtypes.STRING_UUID.toString();
        }
        String syntaxOid = ldapAttribute.getSyntaxOid();
        if (syntaxOid == null) {
            return null;
        }
        if (SYNTAX_MAP.get(syntaxOid) == null) {
            if (icfType == String.class) {
                return AttributeInfo.Subtypes.STRING_CASE_IGNORE.toString();
            } else {
                return null;
            }
        }
        return SYNTAX_MAP.get(syntaxOid).subtype;
    }

    private boolean hasEqualityMatching(AttributeType ldapAttribute, String matchingRuleName,
            String matchingRuleOid) {
        if (ldapAttribute == null) {
            return false;
        }
        if (ldapAttribute.getEquality() != null
                && matchingRuleOid.equalsIgnoreCase(ldapAttribute.getEquality().getOid())) {
            return true;
        }
        if (matchingRuleOid.equalsIgnoreCase(ldapAttribute.getEqualityOid())) {
            return true;
        }
        if (matchingRuleName.equalsIgnoreCase(ldapAttribute.getEqualityName())) {
            return true;
        }
        if (ldapAttribute.getSuperior() != null) {
            if (hasEqualityMatching(ldapAttribute.getSuperior(), matchingRuleName, matchingRuleOid)) {
                return true;
            }
        }
        return false;
    }

    public List<Value<Object>> toLdapValues(AttributeType ldapAttributeType, List<Object> icfAttributeValues) {
        List<Value<Object>> ldapValues = new ArrayList<>(icfAttributeValues.size());
        for (Object icfValue : icfAttributeValues) {
            ldapValues.add(toLdapValue(ldapAttributeType, icfValue));
        }
        return ldapValues;
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public Value<Object> toLdapValue(AttributeType ldapAttributeType, Object icfAttributeValue) {
        if (icfAttributeValue == null) {
            return null;
        }
        if (ldapAttributeType == null) {
            // We have no definition for this attribute. Assume string.
            return (Value) new StringValue(icfAttributeValue.toString());
        }

        if (ldapAttributeType.getName().equalsIgnoreCase(configuration.getPasswordAttribute())) {
            return toLdapPasswordValue(ldapAttributeType, icfAttributeValue);
        }

        return wrapInLdapValueClass(ldapAttributeType, icfAttributeValue);
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    protected Value<Object> wrapInLdapValueClass(AttributeType ldapAttributeType, Object icfAttributeValue) {
        String syntaxOid = ldapAttributeType.getSyntaxOid();
        if (SchemaConstants.GENERALIZED_TIME_SYNTAX.equals(syntaxOid)) {
            if (icfAttributeValue instanceof Long) {
                try {
                    return (Value) new StringValue(ldapAttributeType, LdapUtil
                            .toGeneralizedTime((Long) icfAttributeValue, acceptsFractionalGeneralizedTime()));
                } catch (LdapInvalidAttributeValueException e) {
                    throw new IllegalArgumentException("Invalid value for attribute " + ldapAttributeType.getName()
                            + ": " + e.getMessage() + "; attributeType=" + ldapAttributeType, e);
                }
            } else {
                try {
                    return (Value) new StringValue(ldapAttributeType, icfAttributeValue.toString());
                } catch (LdapInvalidAttributeValueException e) {
                    throw new IllegalArgumentException("Invalid value for attribute " + ldapAttributeType.getName()
                            + ": " + e.getMessage() + "; attributeType=" + ldapAttributeType, e);
                }
            }
        } else if (icfAttributeValue instanceof Boolean) {
            LOG.ok("Converting to LDAP: {0} ({1}): boolean", ldapAttributeType.getName(), syntaxOid);
            try {
                return (Value) new StringValue(ldapAttributeType, icfAttributeValue.toString().toUpperCase());
            } catch (LdapInvalidAttributeValueException e) {
                throw new IllegalArgumentException("Invalid value for attribute " + ldapAttributeType.getName()
                        + ": " + e.getMessage() + "; attributeType=" + ldapAttributeType, e);
            }
        } else if (icfAttributeValue instanceof GuardedString) {
            try {
                return (Value) new GuardedStringValue(ldapAttributeType, (GuardedString) icfAttributeValue);
            } catch (LdapInvalidAttributeValueException e) {
                throw new IllegalArgumentException("Invalid value for attribute " + ldapAttributeType.getName()
                        + ": " + e.getMessage() + "; attributeType=" + ldapAttributeType, e);
            }
        } else if (isBinarySyntax(syntaxOid)) {
            LOG.ok("Converting to LDAP: {0} ({1}): explicit binary", ldapAttributeType.getName(), syntaxOid);

            if (icfAttributeValue instanceof byte[]) {
                try {
                    // Do NOT set attributeType in the Value in this case.
                    // The attributeType might not match the Value class
                    // e.g. human-readable jpegPhoto attribute will expect StringValue
                    return (Value) new BinaryValue(null, (byte[]) icfAttributeValue);
                } catch (LdapInvalidAttributeValueException e) {
                    throw new IllegalArgumentException("Invalid value for attribute " + ldapAttributeType.getName()
                            + ": " + e.getMessage() + "; attributeType=" + ldapAttributeType, e);
                }
            } else if (icfAttributeValue instanceof String) {
                // this can happen for userPassword
                byte[] bytes;
                try {
                    bytes = ((String) icfAttributeValue).getBytes("UTF-8");
                } catch (UnsupportedEncodingException e) {
                    throw new IllegalArgumentException(
                            "Cannot encode attribute value to UTF-8 for attribute " + ldapAttributeType.getName()
                                    + ": " + e.getMessage() + "; attributeType=" + ldapAttributeType,
                            e);
                }
                try {
                    // Do NOT set attributeType in the Value in this case.
                    // The attributeType might not match the Value class
                    // e.g. human-readable jpegPhoto attribute will expect StringValue
                    return (Value) new BinaryValue(null, bytes);
                } catch (LdapInvalidAttributeValueException e) {
                    throw new IllegalArgumentException("Invalid value for attribute " + ldapAttributeType.getName()
                            + ": " + e.getMessage() + "; attributeType=" + ldapAttributeType, e);
                }
            } else {
                throw new IllegalArgumentException(
                        "Invalid value for attribute " + ldapAttributeType.getName() + ": expected byte[] but got "
                                + icfAttributeValue.getClass() + "; attributeType=" + ldapAttributeType);
            }
        } else if (!isBinaryAttribute(syntaxOid)) {
            LOG.ok("Converting to LDAP: {0} ({1}): explicit string", ldapAttributeType.getName(), syntaxOid);
            try {
                return (Value) new StringValue(ldapAttributeType, icfAttributeValue.toString());
            } catch (LdapInvalidAttributeValueException e) {
                throw new IllegalArgumentException("Invalid value for attribute " + ldapAttributeType.getName()
                        + ": " + e.getMessage() + "; attributeType=" + ldapAttributeType, e);
            }
        } else {
            if (icfAttributeValue instanceof byte[]) {
                LOG.ok("Converting to LDAP: {0} ({1}): detected binary", ldapAttributeType.getName(), syntaxOid);
                try {
                    return (Value) new BinaryValue(ldapAttributeType, (byte[]) icfAttributeValue);
                } catch (LdapInvalidAttributeValueException e) {
                    throw new IllegalArgumentException("Invalid value for attribute " + ldapAttributeType.getName()
                            + ": " + e.getMessage() + "; attributeType=" + ldapAttributeType, e);
                }
            } else {
                LOG.ok("Converting to LDAP: {0} ({1}): detected string", ldapAttributeType.getName(), syntaxOid);
                try {
                    return (Value) new StringValue(ldapAttributeType, icfAttributeValue.toString());
                } catch (LdapInvalidAttributeValueException e) {
                    throw new IllegalArgumentException("Invalid value for attribute " + ldapAttributeType.getName()
                            + ": " + e.getMessage() + "; attributeType=" + ldapAttributeType, e);
                }
            }
        }
    }

    protected Value<Object> toLdapPasswordValue(AttributeType ldapAttributeType, Object icfAttributeValue) {
        if (configuration.getPasswordHashAlgorithm() != null && !LdapConfiguration.PASSWORD_HASH_ALGORITHM_NONE
                .equals(configuration.getPasswordHashAlgorithm())) {
            icfAttributeValue = hashLdapPassword(icfAttributeValue);
        }
        return wrapInLdapValueClass(ldapAttributeType, icfAttributeValue);
    }

    protected boolean acceptsFractionalGeneralizedTime() {
        return true;
    }

    /**
     * Used to parse __UID__ and __NAME__.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public Value<Object> toLdapIdentifierValue(AttributeType ldapAttributeType, String icfAttributeValue) {
        if (icfAttributeValue == null) {
            return null;
        }
        if (ldapAttributeType == null) {
            // We have no definition for this attribute. Assume string.
            return (Value) new StringValue(icfAttributeValue);
        }

        String syntaxOid = ldapAttributeType.getSyntaxOid();
        if (SchemaConstants.OCTET_STRING_SYNTAX.equals(syntaxOid)) {
            // Expect hex-encoded value (see toIcfIdentifierValue())
            byte[] bytes = LdapUtil.hexToBinary(icfAttributeValue);
            // Do NOT set attributeType in the Value in this case.
            // The attributeType might not match the Value class
            return (Value) new BinaryValue(bytes);
        } else {
            try {
                return (Value) new StringValue(ldapAttributeType, icfAttributeValue);
            } catch (LdapInvalidAttributeValueException e) {
                throw new IllegalArgumentException("Invalid value for attribute " + ldapAttributeType.getName()
                        + ": " + e.getMessage() + "; attributeType=" + ldapAttributeType, e);
            }
        }
    }

    public Value<Object> toLdapValue(AttributeType ldapAttributeType, List<Object> icfAttributeValues) {
        if (icfAttributeValues == null || icfAttributeValues.isEmpty()) {
            return null;
        }
        if (icfAttributeValues.size() > 1) {
            throw new IllegalArgumentException(
                    "More than one value specified for LDAP attribute " + ldapAttributeType.getName());
        }
        return toLdapValue(ldapAttributeType, icfAttributeValues.get(0));
    }

    private Object toIcfValue(String icfAttributeName, Value<?> ldapValue, String ldapAttributeName,
            AttributeType ldapAttributeType) {
        if (ldapValue == null) {
            return null;
        }
        if (OperationalAttributeInfos.PASSWORD.is(icfAttributeName)) {
            return new GuardedString(ldapValue.getString().toCharArray());
        } else {
            String syntaxOid = null;
            if (ldapAttributeType != null) {
                syntaxOid = ldapAttributeType.getSyntaxOid();
            }
            if (SchemaConstants.GENERALIZED_TIME_SYNTAX.equals(syntaxOid)) {
                if (AbstractLdapConfiguration.TIMESTAMP_PRESENTATION_UNIX_EPOCH
                        .equals(getConfiguration().getTimestampPresentation())) {
                    try {
                        GeneralizedTime gt = new GeneralizedTime(ldapValue.getString());
                        return gt.getCalendar().getTimeInMillis();
                    } catch (ParseException e) {
                        throw new InvalidAttributeValueException("Wrong generalized time format in LDAP attribute "
                                + ldapAttributeName + ": " + e.getMessage(), e);
                    }
                } else {
                    return ldapValue.getString();
                }
            } else if (SchemaConstants.BOOLEAN_SYNTAX.equals(syntaxOid)) {
                return Boolean.parseBoolean(ldapValue.getString());
            } else if (isIntegerSyntax(syntaxOid)) {
                return Integer.parseInt(ldapValue.getString());
            } else if (isLongSyntax(syntaxOid)) {
                return Long.parseLong(ldapValue.getString());
            } else if (isBinarySyntax(syntaxOid)) {
                LOG.ok("Converting to ICF: {0} (syntax {1}, value {2}): explicit binary", ldapAttributeName,
                        syntaxOid, ldapValue.getClass());
                return ldapValue.getBytes();
            } else if (isStringSyntax(syntaxOid)) {
                LOG.ok("Converting to ICF: {0} (syntax {1}, value {2}): explicit string", ldapAttributeName,
                        syntaxOid, ldapValue.getClass());
                return ldapValue.getString();
            } else {
                if (ldapValue instanceof StringValue) {
                    LOG.ok("Converting to ICF: {0} (syntax {1}, value {2}): detected string", ldapAttributeName,
                            syntaxOid, ldapValue.getClass());
                    return ldapValue.getString();
                } else {
                    LOG.ok("Converting to ICF: {0} (syntax {1}, value {2}): detected binary", ldapAttributeName,
                            syntaxOid, ldapValue.getClass());
                    return ldapValue.getBytes();
                }
            }
        }
    }

    protected boolean isIntegerSyntax(String syntaxOid) {
        return SchemaConstants.INTEGER_SYNTAX.equals(syntaxOid);
    }

    protected boolean isLongSyntax(String syntaxOid) {
        return SchemaConstants.JAVA_LONG_SYNTAX.equals(syntaxOid)
                || LdapConstants.SYNTAX_AD_INTEGER8_SYNTAX.equals(syntaxOid);
    }

    /**
     * Tells if the given Syntax OID is String. It checks only a subset of
     * know syntaxes :
     * <ul>
     *   <li>DIRECTORY_STRING_SYNTAX</li>
     *   <li>IA5_STRING_SYNTAX</li>
     *   <li>OBJECT_CLASS_TYPE_SYNTAX</li>
     *   <li>DN_SYNTAX</li>
     *   <li>PRINTABLE_STRING_SYNTAX</li>
     * </ul>  
     * @param syntaxOid The Syntax OID
     * @return <tt>true</tt> if the syntax OID is one of the listed syntaxes
     */
    protected boolean isStringSyntax(String syntaxOid) {
        if (syntaxOid == null) {
            // If there is no syntax information we assume that is is string type
            return true;
        }
        switch (syntaxOid) {
        case SchemaConstants.DIRECTORY_STRING_SYNTAX:
        case SchemaConstants.IA5_STRING_SYNTAX:
        case SchemaConstants.OBJECT_CLASS_TYPE_SYNTAX:
        case SchemaConstants.DN_SYNTAX:
        case SchemaConstants.PRINTABLE_STRING_SYNTAX:
            return true;
        default:
            return false;
        }
    }

    /**
     * Tells if the given Syntax OID is binary. It checks only a subset of
     * know syntaxes :
     * <ul>
     *   <li>OCTET_STRING_SYNTAX</li>
     *   <li>JPEG_SYNTAX</li>
     *   <li>BINARY_SYNTAX</li>
     *   <li>BIT_STRING_SYNTAX</li>
     *   <li>CERTIFICATE_SYNTAX</li>
     *   <li>CERTIFICATE_LIST_SYNTAX</li>
     *   <li>CERTIFICATE_PAIR_SYNTAX</li>
     * </ul>  
     * @param syntaxOid The Syntax OID
     * @return <tt>true</tt> if the syntax OID is one of the listed syntaxes
     */
    protected boolean isBinarySyntax(String syntaxOid) {
        if (syntaxOid == null) {
            return false;
        }
        switch (syntaxOid) {
        case SchemaConstants.OCTET_STRING_SYNTAX:
        case SchemaConstants.JPEG_SYNTAX:
        case SchemaConstants.BINARY_SYNTAX:
        case SchemaConstants.BIT_STRING_SYNTAX:
        case SchemaConstants.CERTIFICATE_SYNTAX:
        case SchemaConstants.CERTIFICATE_LIST_SYNTAX:
        case SchemaConstants.CERTIFICATE_PAIR_SYNTAX:
            return true;
        default:
            return false;
        }
    }

    /**
     * Check if an Attribute is binary or String. We use either the H/R flag, if present,
     * or a set of static syntaxes. In this case, here are the statically defined matches :
     * <ul>
      *   <li>
      *     Binary syntaxes :
      *     <ul>
      *       <li>BINARY_SYNTAX</li>
      *       <li>BIT_STRING_SYNTAX</li>
      *       <li>CERTIFICATE_LIST_SYNTAX</li>
      *       <li>CERTIFICATE_PAIR_SYNTAX</li>
      *       <li>CERTIFICATE_SYNTAX</li>
      *       <li>JPEG_SYNTAX</li>
      *       <li>OCTET_STRING_SYNTAX</li>
      *     </ul>
      *   </li>
      *   <li>
      *     String syntaxes :
      *     <ul>
      *       <li>DIRECTORY_STRING_SYNTAX</li>
      *       <li>DN_SYNTAX</li>
      *       <li>IA5_STRING_SYNTAX</li>
      *       <li>OBJECT_CLASS_TYPE_SYNTAX</li>
      *       <li>PRINTABLE_STRING_SYNTAX</li>
      *     </ul>
      *   </li>
      * </ul>
      * 
      * @param attributeId The Attribute name or its OID
      * @return <tt>true</tt> if the attribute is binary, <tt>false</tt> otherwise
      */
    public boolean isBinaryAttribute(String attributeId) {
        // Get rid of the attribute's options
        String ldapAttributeName = getLdapAttributeName(attributeId);

        // Retrieve the attributeType from the schema
        AttributeType attributeType = schemaManager.getAttributeType(ldapAttributeName);

        if (attributeType == null) {
            // Not found. Let's try with the set of hard-coded attributeType
            if (STRING_ATTRIBUTE_NAMES.contains(attributeId.toLowerCase())) {
                return false;
            }

            LOG.warn("Uknown attribute {0}, cannot determine if it is binary", ldapAttributeName);

            return false;
        }

        // Ok, we have the AttributeType, let's get its Syntax
        LdapSyntax syntax = getSyntax(attributeType);

        // Should *never* happen, as the getSyntax() method always 
        // return a syntax....
        if (syntax == null) {
            // OpenLDAP does not define some syntaxes that it uses
            return false;
        }

        String syntaxOid = syntax.getOid();

        // First check in the pre-defined list, just in case
        if (isBinarySyntax(syntaxOid)) {
            return true;
        }

        if (isStringSyntax(syntaxOid)) {
            return false;
        }

        // Ok, if the syntax is not one of the pre-defined we know of, 
        // try to ask the syntax about its status.
        return !syntax.isHumanReadable();
    }

    /**
     * Retrieve the Syntax associated with an AttributeType. In theory, every AttributeType
     * must have a syntax, but some rogue and not compliant LDAP Servers don't do that.
     * Typically, if an AttributeType does not have a Syntax, then it should inherit from
     * its parent's Syntax.  
     * 
     * @param attributeType The AttributeType for which we want the Syntax
     * @return The LdapSyntax instance for this AttributeType
     */
    LdapSyntax getSyntax(AttributeType attributeType) {
        LdapSyntax syntax = attributeType.getSyntax();

        if (syntax == null && attributeType.getSyntaxOid() != null) {
            // HACK to support ugly servers (such as AD) that do not declare 
            // ldapSyntaxes in the schema
            // We will first check if we can't find the syntax from the
            // SchemaManager, and if not, we will create it
            try {
                syntax = schemaManager.lookupLdapSyntaxRegistry(attributeType.getSyntaxOid());
            } catch (LdapException e) {
                // Fallback...
                syntax = new LdapSyntax(attributeType.getSyntaxOid());
            }
        }

        return syntax;
    }

    /**
     * Used to format __UID__ and __NAME__.
     */
    public String toIcfIdentifierValue(Value<?> ldapValue, String ldapAttributeName,
            AttributeType ldapAttributeType) {
        if (ldapValue == null) {
            return null;
        }
        if (ldapAttributeType == null) {
            // E.g. ancient OpenLDAP does not have entryUUID in schema
            if (!configuration.isAllowUnknownAttributes()) {
                throw new InvalidAttributeValueException(
                        "Unknown LDAP attribute " + ldapAttributeName + " (not present in LDAP schema)");
            }
        }

        if ((ldapAttributeType != null) && isBinaryAttribute(ldapAttributeName)) {
            LOG.ok("Converting identifier to ICF: {0} (syntax {1}, value {2}): explicit binary", ldapAttributeName,
                    getSyntax(ldapAttributeType).getOid(), ldapValue.getClass());

            byte[] bytes;

            if (ldapValue instanceof BinaryValue) {
                bytes = ldapValue.getBytes();
            } else if (ldapValue instanceof StringValue) {
                // Binary value incorrectly detected as string value. Conversion to Java string has broken the data.
                // We need to do some magic to fix it.
                bytes = ldapValue.getBytes();
            } else {
                throw new IllegalStateException("Unexpected value type " + ldapValue.getClass());
            }

            // Assume that identifiers are short. It is more readable to use hex representation than base64.
            return LdapUtil.binaryToHex(bytes);
        } else {
            LOG.ok("Converting identifier to ICF: {0} (syntax {1}, value {2}): implicit string", ldapAttributeName,
                    ldapAttributeType == null ? null : getSyntax(ldapAttributeType).getOid(), ldapValue.getClass());

            return ldapValue.getString();
        }
    }

    public ObjectClassInfo findObjectClassInfo(ObjectClass icfObjectClass) {
        return icfSchema.findObjectClassInfo(icfObjectClass.getObjectClassValue());
    }

    /**
     * Tells if a given Entry has an UID attribute
     * 
     * @param entry The Entry to check
     * @return <tt>true</tt> if the entry contains an UID attribute
     */
    public boolean hasUidAttribute(Entry entry) {
        String uidAttributeName = configuration.getUidAttribute();

        if (LdapUtil.isDnAttribute(uidAttributeName)) {
            return true;
        } else {
            return entry.get(uidAttributeName) != null;
        }
    }

    public ConnectorObject toIcfObject(LdapNetworkConnection connection, ObjectClass icfObjectClass, Entry entry,
            AttributeHandler attributeHandler) {
        ObjectClassInfo icfObjectClassInfo = findObjectClassInfo(icfObjectClass);
        if (icfObjectClassInfo == null) {
            throw new InvalidAttributeValueException("No definition for object class " + icfObjectClass);
        }
        return toIcfObject(connection, icfObjectClassInfo, entry, null, attributeHandler);
    }

    public ConnectorObject toIcfObject(LdapNetworkConnection connection,
            ObjectClassInfo icfStructuralObjectClassInfo, Entry entry) {
        return toIcfObject(connection, icfStructuralObjectClassInfo, entry, null, null);
    }

    public ConnectorObject toIcfObject(LdapNetworkConnection connection,
            ObjectClassInfo icfStructuralObjectClassInfo, Entry entry, String dn) {
        return toIcfObject(connection, icfStructuralObjectClassInfo, entry, dn, null);
    }

    public ConnectorObject toIcfObject(LdapNetworkConnection connection,
            ObjectClassInfo icfStructuralObjectClassInfo, Entry entry, String dn,
            AttributeHandler attributeHandler) {
        LdapObjectClasses ldapObjectClasses = processObjectClasses(entry);
        if (icfStructuralObjectClassInfo == null) {
            icfStructuralObjectClassInfo = icfSchema
                    .findObjectClassInfo(ldapObjectClasses.getLdapLowestStructuralObjectClass().getName());
        }
        ConnectorObjectBuilder cob = new ConnectorObjectBuilder();
        if (dn == null) {
            dn = getDn(entry);
        }
        cob.setName(dn);
        cob.setObjectClass(new ObjectClass(icfStructuralObjectClassInfo.getType()));

        List<ObjectClassInfo> icfAuxiliaryObjectClassInfos = new ArrayList<>(
                ldapObjectClasses.getLdapAuxiliaryObjectClasses().size());
        if (!ldapObjectClasses.getLdapAuxiliaryObjectClasses().isEmpty()) {
            AttributeBuilder auxAttrBuilder = new AttributeBuilder();
            auxAttrBuilder.setName(PredefinedAttributes.AUXILIARY_OBJECT_CLASS_NAME);
            for (org.apache.directory.api.ldap.model.schema.ObjectClass ldapAuxiliaryObjectClass : ldapObjectClasses
                    .getLdapAuxiliaryObjectClasses()) {
                auxAttrBuilder.addValue(ldapAuxiliaryObjectClass.getName());
                ObjectClassInfo objectClassInfo = icfSchema.findObjectClassInfo(ldapAuxiliaryObjectClass.getName());
                //            LOG.ok("ConnId object class info for auxiliary object class {0}:\n{1}", ldapAuxiliaryObjectClass.getName(), objectClassInfo);
                icfAuxiliaryObjectClassInfos.add(objectClassInfo);
            }
            cob.addAttribute(auxAttrBuilder.build());
        }

        String uidAttributeName = configuration.getUidAttribute();
        String uid;
        if (LdapUtil.isDnAttribute(uidAttributeName)) {
            uid = dn;
        } else {
            org.apache.directory.api.ldap.model.entry.Attribute uidAttribute = entry.get(uidAttributeName);
            if (uidAttribute == null) {
                throw new IllegalArgumentException(
                        "LDAP entry " + dn + " does not have UID attribute " + uidAttributeName);
            }
            if (uidAttribute.size() > 1) {
                throw new IllegalArgumentException(
                        "LDAP entry " + dn + " has more than one value for UID attribute " + uidAttributeName);
            }
            AttributeType attributeType = schemaManager.getAttributeType(uidAttribute.getId());
            uid = toIcfIdentifierValue(uidAttribute.get(), uidAttribute.getId(), attributeType);
        }
        cob.setUid(uid);

        Iterator<org.apache.directory.api.ldap.model.entry.Attribute> iterator = entry.iterator();
        while (iterator.hasNext()) {
            org.apache.directory.api.ldap.model.entry.Attribute ldapAttribute = iterator.next();
            String ldapAttrName = getLdapAttributeName(ldapAttribute);
            //         LOG.ok("Processing attribute {0}", ldapAttrName);
            if (!shouldTranslateAttribute(ldapAttrName)) {
                //            LOG.ok("Should not translate attribute {0}, skipping", ldapAttrName);
                continue;
            }
            AttributeType attributeType = schemaManager.getAttributeType(ldapAttrName);
            //         LOG.ok("Type for attribute {0}: {1}", ldapAttrName, attributeType);
            String ldapAttributeNameFromSchema = ldapAttrName;
            if (attributeType == null) {
                if (!configuration.isAllowUnknownAttributes()) {
                    throw new InvalidAttributeValueException(
                            "Unknown LDAP attribute " + ldapAttrName + " (not present in LDAP schema)");
                }
            } else {
                ldapAttributeNameFromSchema = attributeType.getName();
            }
            if (uidAttributeName.equals(ldapAttributeNameFromSchema)) {
                continue;
            }
            Attribute icfAttribute = toIcfAttribute(connection, entry, ldapAttribute, attributeHandler);
            //         LOG.ok("ConnId attribute for {0}: {1}", ldapAttrName, icfAttribute);
            if (icfAttribute == null) {
                continue;
            }
            AttributeInfo attributeInfo = SchemaUtil.findAttributeInfo(icfStructuralObjectClassInfo, icfAttribute);
            if (attributeInfo == null) {
                for (ObjectClassInfo icfAuxiliaryObjectClassInfo : icfAuxiliaryObjectClassInfos) {
                    attributeInfo = SchemaUtil.findAttributeInfo(icfAuxiliaryObjectClassInfo, icfAttribute);
                    //               LOG.ok("Looking for ConnId attribute {0} info in auxiliary class {1}: {2}", icfAttribute, icfAuxiliaryObjectClassInfo==null?null:icfAuxiliaryObjectClassInfo.getType(), attributeInfo);
                    if (attributeInfo != null) {
                        break;
                    }
                    //               LOG.ok("Failed to find attribute in: {0}", icfAuxiliaryObjectClassInfo);
                }
            }
            //         LOG.ok("ConnId attribute info for {0} ({1}): {2}", icfAttribute.getName(), ldapAttrName, attributeInfo);
            if (attributeInfo != null) {
                // Avoid sending unknown attributes (such as createtimestamp)
                cob.addAttribute(icfAttribute);
            } else {
                LOG.ok("ConnId attribute {0} is not part of ConnId schema, skipping", icfAttribute.getName());
            }

        }

        extendConnectorObject(cob, entry, icfStructuralObjectClassInfo.getType());

        return cob.build();
    }

    public String getDn(Entry entry) {
        return entry.getDn().getName();
    }

    public String getLdapAttributeName(org.apache.directory.api.ldap.model.entry.Attribute ldapAttribute) {
        return getLdapAttributeName(ldapAttribute.getId());
    }

    /**
     * Get back the attribute name, without the options. Typically, RFC 4512 
     * defines an Attribute description as :
     * <pre>
     * attributedescription = attributetype options
      * attributetype = oid
      * options = *( SEMI option )
      * option = 1*keychar
     * </pre>
     * 
     * where <em>oid</em> can be a String or an OID. An example is :
     * <pre>
     * cn;lang-de;lang-en
     * </pre>
     * where the attribute name is <em>cn</em>.
     * <p>
     * @param attributeId The attribute descriptio to parse
     * @return The attribute name, without the options
     */
    public String getLdapAttributeName(String attributeId) {
        int iSemicolon = attributeId.indexOf(';');

        if (iSemicolon < 0) {
            return attributeId;
        }

        return attributeId.substring(0, iSemicolon);
    }

    protected boolean shouldTranslateAttribute(String attrName) {
        return true;
    }

    protected void extendConnectorObject(ConnectorObjectBuilder cob, Entry entry, String objectClassName) {
        // Nothing to do here. This is supposed to be overriden by subclasses.
    }

    private LdapObjectClasses processObjectClasses(Entry entry) {
        LdapObjectClasses ocs = new LdapObjectClasses();
        org.apache.directory.api.ldap.model.entry.Attribute objectClassAttribute = entry
                .get(SchemaConstants.OBJECT_CLASS_AT);
        if (objectClassAttribute == null) {
            throw new InvalidAttributeValueException("No object class attribute in entry " + entry.getDn());
        }
        // Neither structural nor auxiliary. Should not happen. But it does.
        List<org.apache.directory.api.ldap.model.schema.ObjectClass> outstandingObjectClasses = new ArrayList<>();
        for (Value<?> objectClassVal : objectClassAttribute) {
            String objectClassString = objectClassVal.getString();
            org.apache.directory.api.ldap.model.schema.ObjectClass ldapObjectClass;
            try {
                ldapObjectClass = schemaManager.lookupObjectClassRegistry(objectClassString);
            } catch (LdapException e) {
                throw new InvalidAttributeValueException(e.getMessage(), e);
            }
            if (ldapObjectClass.isStructural()) {
                //            LOG.ok("Objectclass {0}: structural)", ldapObjectClass.getName());
                ocs.getLdapStructuralObjectClasses().add(ldapObjectClass);
            } else if (ldapObjectClass.isAuxiliary()) {
                //            LOG.ok("Objectclass {0}: auxiliary)", ldapObjectClass.getName());
                ocs.getLdapAuxiliaryObjectClasses().add(ldapObjectClass);
            } else if (ldapObjectClass.isAbstract()) {
                //            LOG.ok("Objectclass {0}: abstract)", ldapObjectClass.getName());
                // We are ignoring this. This is 'top' and things like that.
                // These are not directly useful, not even in the alternative mechanism.
            } else {
                //            LOG.ok("Objectclass {0}: outstanding)", ldapObjectClass.getName());
                outstandingObjectClasses.add(ldapObjectClass);
            }
        }
        if (ocs.getLdapStructuralObjectClasses().isEmpty()) {
            throw new InvalidAttributeValueException(
                    "Entry " + entry.getDn() + " has no structural object classes");
        }
        if (ocs.getLdapStructuralObjectClasses().size() == 1) {
            ocs.setLdapLowestStructuralObjectClass(ocs.getLdapStructuralObjectClasses().get(0));
        } else {
            for (org.apache.directory.api.ldap.model.schema.ObjectClass structObjectClass : ocs
                    .getLdapStructuralObjectClasses()) {
                if (!hasSubclass(structObjectClass, ocs.getLdapStructuralObjectClasses())) {
                    ocs.setLdapLowestStructuralObjectClass(structObjectClass);
                    break;
                }
            }
            if (ocs.getLdapLowestStructuralObjectClass() == null) {
                throw new InvalidAttributeValueException(
                        "Cannot determine lowest structural object class for set of object classes: "
                                + objectClassAttribute);
            }
        }
        if (getConfiguration().isAlternativeObjectClassDetection()) {
            for (org.apache.directory.api.ldap.model.schema.ObjectClass objectClass : outstandingObjectClasses) {
                // Extra filter to filter out classes such as 'top' if they are not
                // properly marked as abstract
                if (hasSubclass(objectClass, outstandingObjectClasses)) {
                    continue;
                }
                if (hasSubclass(objectClass, ocs.getLdapStructuralObjectClasses())) {
                    continue;
                }
                if (hasSubclass(objectClass, ocs.getLdapAuxiliaryObjectClasses())) {
                    continue;
                }
                LOG.ok("Detected auxliary objectclasse (alternative method): {0})", ocs);
                ocs.getLdapAuxiliaryObjectClasses().addAll(outstandingObjectClasses);
            }
        }
        //      LOG.ok("Detected objectclasses: {0})", ocs);
        return ocs;
    }

    private boolean hasSubclass(org.apache.directory.api.ldap.model.schema.ObjectClass objectClass,
            List<org.apache.directory.api.ldap.model.schema.ObjectClass> otherObjectClasses) {
        //      LOG.ok("Trying {0} ({1})", structObjectClass.getName(), structObjectClass.getOid());
        for (org.apache.directory.api.ldap.model.schema.ObjectClass otherObjectClass : otherObjectClasses) {
            if (objectClass.getOid().equals(otherObjectClass.getOid())) {
                continue;
            }
            //         LOG.ok("  with {0} ({1})", otherObjectClass.getName(), structObjectClass.getOid());
            //         LOG.ok("    superiorOids: {0}", otherObjectClass.getSuperiorOids());
            if (otherObjectClass.getSuperiorOids().contains(objectClass.getOid())
                    || otherObjectClass.getSuperiorOids().contains(objectClass.getName())) {
                //            LOG.ok("    hasSubclass");
                return true;
            }
        }
        return false;
    }

    private Object hashLdapPassword(Object icfAttributeValue) {
        if (icfAttributeValue == null) {
            return null;
        }
        byte[] bytes;
        if (icfAttributeValue instanceof String) {
            try {
                bytes = ((String) icfAttributeValue).getBytes("UTF-8");
            } catch (UnsupportedEncodingException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
        } else if (icfAttributeValue instanceof GuardedString) {
            final String[] out = new String[1];
            ((GuardedString) icfAttributeValue).access(new GuardedString.Accessor() {
                @Override
                public void access(char[] clearChars) {
                    out[0] = new String(clearChars);
                }
            });
            try {
                bytes = out[0].getBytes("UTF-8");
            } catch (UnsupportedEncodingException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
        } else if (icfAttributeValue instanceof byte[]) {
            bytes = (byte[]) icfAttributeValue;
        } else {
            throw new InvalidAttributeValueException(
                    "Unsupported type of password attribute: " + icfAttributeValue.getClass());
        }
        return hashBytes(bytes, configuration.getPasswordHashAlgorithm(), 0);
    }

    private String hashBytes(byte[] clear, String alg, long seed) {
        MessageDigest md = null;

        try {
            if (alg.equalsIgnoreCase("SSHA") || alg.equalsIgnoreCase("SHA")) {
                md = MessageDigest.getInstance("SHA-1");
            } else if (alg.equalsIgnoreCase("SMD5") || alg.equalsIgnoreCase("MD5")) {
                md = MessageDigest.getInstance("MD5");
            }
        } catch (NoSuchAlgorithmException e) {
            throw new ConnectorException("Could not find MessageDigest algorithm: " + alg);
        }

        if (md == null) {
            throw new ConnectorException("Unsupported MessageDigest algorithm: " + alg);
        }

        byte[] salt = {};
        if (alg.equalsIgnoreCase("SSHA") || alg.equalsIgnoreCase("SMD5")) {
            Random rnd = new Random();
            rnd.setSeed(System.currentTimeMillis() + seed);
            salt = new byte[8];
            rnd.nextBytes(salt);
        }

        md.reset();
        md.update(clear);
        md.update(salt);
        byte[] hash = md.digest();

        byte[] hashAndSalt = new byte[hash.length + salt.length];
        System.arraycopy(hash, 0, hashAndSalt, 0, hash.length);
        System.arraycopy(salt, 0, hashAndSalt, hash.length, salt.length);

        StringBuilder resSb = new StringBuilder(alg.length() + hashAndSalt.length);
        resSb.append('{');
        resSb.append(alg);
        resSb.append('}');
        resSb.append(Base64.encode(hashAndSalt));

        return resSb.toString();
    }

    private Attribute toIcfAttribute(LdapNetworkConnection connection, Entry entry,
            org.apache.directory.api.ldap.model.entry.Attribute ldapAttribute, AttributeHandler attributeHandler) {
        AttributeBuilder ab = new AttributeBuilder();
        String ldapAttributeName = getLdapAttributeName(ldapAttribute);
        AttributeType ldapAttributeType = schemaManager.getAttributeType(ldapAttributeName);
        String ldapAttributeNameFromSchema;
        if (ldapAttributeType == null) {
            if (configuration.isAllowUnknownAttributes()) {
                ldapAttributeNameFromSchema = ldapAttributeName;
            } else {
                throw new InvalidAttributeValueException(
                        "Unknown LDAP attribute " + ldapAttributeName + " (not present in LDAP schema)");
            }
        } else {
            ldapAttributeNameFromSchema = ldapAttributeType.getName();
        }
        String icfAttributeName = toIcfAttributeName(ldapAttributeNameFromSchema);
        ab.setName(icfAttributeName);
        if (attributeHandler != null) {
            attributeHandler.handle(connection, entry, ldapAttribute, ab);
        }
        boolean incompleteRead = false;
        if (OperationalAttributeInfos.PASSWORD.is(icfAttributeName)) {
            switch (configuration.getPasswordReadStrategy()) {
            case AbstractLdapConfiguration.PASSWORD_READ_STRATEGY_READABLE:
                // Nothing to do. Proceed with ordinary read.
                break;
            case AbstractLdapConfiguration.PASSWORD_READ_STRATEGY_INCOMPLETE_READ:
                incompleteRead = true;
                break;
            case AbstractLdapConfiguration.PASSWORD_READ_STRATEGY_UNREADABLE:
                return null;
            default:
                throw new ConfigurationException(
                        "Unknown passoword read strategy " + configuration.getPasswordReadStrategy());
            }
        }
        Iterator<Value<?>> iterator = ldapAttribute.iterator();
        boolean hasValidValue = false;
        while (iterator.hasNext()) {
            Value<?> ldapValue = iterator.next();
            Object icfValue = toIcfValue(icfAttributeName, ldapValue, ldapAttributeNameFromSchema,
                    ldapAttributeType);
            if (icfValue != null) {
                if (!incompleteRead) {
                    ab.addValue(icfValue);
                }
                hasValidValue = true;
            }
        }
        if (!hasValidValue) {
            // Do not even try to build. The build will fail.
            return null;
        }
        if (incompleteRead) {
            ab.setAttributeValueCompleteness(AttributeValueCompleteness.INCOMPLETE);
        }
        try {
            return ab.build();
        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException(
                    e.getMessage() + ", attribute " + icfAttributeName + " (ldap: " + ldapAttributeName + ")", e);
        }
    }

    public Dn toDn(Attribute attribute) {
        if (attribute == null) {
            return null;
        }
        return toDn(SchemaUtil.getSingleStringNonBlankValue(attribute));
    }

    public Dn toDn(Uid icfUid) {
        if (icfUid == null) {
            return null;
        }
        return toDn(icfUid.getUidValue());
    }

    public Dn toDn(String stringDn) {
        if (stringDn == null) {
            return null;
        }
        try {
            return new Dn(stringDn);
        } catch (LdapInvalidDnException e) {
            throw new InvalidAttributeValueException("Invalid DN '" + stringDn + "': " + e.getMessage(), e);
        }
    }

    public Dn toSchemaAwareDn(Attribute attribute) {
        if (attribute == null) {
            return null;
        }
        return toSchemaAwareDn(SchemaUtil.getSingleStringNonBlankValue(attribute));
    }

    public Dn toSchemaAwareDn(Uid icfUid) {
        if (icfUid == null) {
            return null;
        }
        return toSchemaAwareDn(icfUid.getUidValue());
    }

    public Dn toSchemaAwareDn(String stringDn) {
        if (stringDn == null) {
            return null;
        }
        try {
            return new Dn(schemaManager, stringDn);
        } catch (LdapInvalidDnException e) {
            throw new InvalidAttributeValueException("Invalid DN '" + stringDn + "': " + e.getMessage(), e);
        }
    }

    // This may seems strange. But it converts non-schema-aware DNs to schema-aware DNs.
    public Dn toSchemaAwareDn(Dn dn) {
        if (dn == null) {
            return null;
        }
        if (dn.isSchemaAware()) {
            return dn;
        }
        try {
            dn.apply(schemaManager);
        } catch (LdapInvalidDnException e) {
            throw new InvalidAttributeValueException("Invalid DN '" + dn + "': " + e.getMessage(), e);
        }
        return dn;
    }

    /**
     * Find an attribute that is part of the specified object class definition.
     * Returns the first attribute from the list of candidate attributes that matches.
     */
    public String selectAttribute(org.apache.directory.api.ldap.model.schema.ObjectClass ldapObjectClass,
            List<String> candidates) {
        for (String candidate : candidates) {
            if (getConfiguration().getUidAttribute().equalsIgnoreCase(candidate)) {
                return candidate;
            }
            if (hasAttribute(ldapObjectClass, candidate)) {
                return candidate;
            }
        }
        for (org.apache.directory.api.ldap.model.schema.ObjectClass superClass : ldapObjectClass.getSuperiors()) {
            String selectedAttribute = selectAttribute(superClass, candidates);
            if (selectedAttribute != null) {
                return selectedAttribute;
            }
        }
        return null;
    }

    private boolean hasAttribute(org.apache.directory.api.ldap.model.schema.ObjectClass ldapObjectClass,
            String attributeName) {
        if (hasAttribute(ldapObjectClass.getMustAttributeTypes(), attributeName)
                || hasAttribute(ldapObjectClass.getMayAttributeTypes(), attributeName)) {
            return true;
        }
        for (org.apache.directory.api.ldap.model.schema.ObjectClass superior : ldapObjectClass.getSuperiors()) {
            if (superior.getName().equalsIgnoreCase(SchemaConstants.TOP_OC)) {
                // Do not even try top object class. Standard top objectclass has nothing to offer.
                // And some non-standard (e.g. AD) definitions will only screw everything up as they
                // contain definition for attributes that are not really meaningful.
                continue;
            }
            if (hasAttribute(superior, attributeName)) {
                return true;
            }
        }
        return false;
    }

    private boolean hasAttribute(List<AttributeType> attrTypeList, String attributeName) {
        for (AttributeType attrType : attrTypeList) {
            for (String name : attrType.getNames()) {
                if (attributeName.equalsIgnoreCase(name)) {
                    return true;
                }
            }
        }
        return false;
    }

    public String[] getOperationalAttributes() {
        return configuration.getOperationalAttributes();
    }

    public String getUidAttribute() {
        return configuration.getUidAttribute();
    }

    private static class TypeSubType {
        Class<?> type;
        String subtype;

        public TypeSubType(Class<?> type, String subtype) {
            super();
            this.type = type;
            this.subtype = subtype;
        }
    }

    private static void addToSyntaxMap(String syntaxOid, Class<?> type) {
        SYNTAX_MAP.put(syntaxOid, new TypeSubType(type, null));
    }

    private static void addToSyntaxMap(String syntaxOid, Class<?> type, String subtype) {
        SYNTAX_MAP.put(syntaxOid, new TypeSubType(type, subtype));
    }

    private static void addToSyntaxMap(String syntaxOid, Class<?> type, AttributeInfo.Subtypes subtype) {
        SYNTAX_MAP.put(syntaxOid, new TypeSubType(type, subtype.toString()));
    }

    static {
        addToSyntaxMap(SchemaConstants.NAME_OR_NUMERIC_ID_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.OBJECT_CLASS_TYPE_SYNTAX, String.class,
                AttributeInfo.Subtypes.STRING_CASE_IGNORE);
        addToSyntaxMap(SchemaConstants.NUMERIC_OID_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.ATTRIBUTE_TYPE_USAGE_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.NUMBER_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.OID_LEN_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.OBJECT_NAME_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.ACI_ITEM_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.ACCESS_POINT_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.ATTRIBUTE_TYPE_DESCRIPTION_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.AUDIO_SYNTAX, byte[].class);
        addToSyntaxMap(SchemaConstants.BINARY_SYNTAX, byte[].class);
        addToSyntaxMap(SchemaConstants.BIT_STRING_SYNTAX, byte[].class);
        addToSyntaxMap(SchemaConstants.BOOLEAN_SYNTAX, Boolean.class);
        addToSyntaxMap(SchemaConstants.CERTIFICATE_SYNTAX, byte[].class);
        addToSyntaxMap(SchemaConstants.CERTIFICATE_LIST_SYNTAX, byte[].class);
        addToSyntaxMap(SchemaConstants.CERTIFICATE_PAIR_SYNTAX, byte[].class);
        addToSyntaxMap(SchemaConstants.COUNTRY_STRING_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.DN_SYNTAX, String.class, AttributeInfo.Subtypes.STRING_LDAP_DN);
        addToSyntaxMap(SchemaConstants.DATA_QUALITY_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.DELIVERY_METHOD_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.DIRECTORY_STRING_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.DIT_CONTENT_RULE_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.DIT_STRUCTURE_RULE_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.DL_SUBMIT_PERMISSION_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.DSA_QUALITY_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.DSE_TYPE_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.ENHANCED_GUIDE_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.FACSIMILE_TELEPHONE_NUMBER_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.FAX_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.GENERALIZED_TIME_SYNTAX, Date.class); // Date.class is a placeholder. It will be replaced by real value in the main code
        addToSyntaxMap(SchemaConstants.GUIDE_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.IA5_STRING_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.INTEGER_SYNTAX, int.class);
        addToSyntaxMap(SchemaConstants.JPEG_SYNTAX, byte[].class);
        addToSyntaxMap(SchemaConstants.MASTER_AND_SHADOW_ACCESS_POINTS_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.MATCHING_RULE_DESCRIPTION_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.MATCHING_RULE_USE_DESCRIPTION_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.MAIL_PREFERENCE_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.MHS_OR_ADDRESS_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.NAME_AND_OPTIONAL_UID_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.NAME_FORM_DESCRIPTION_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.NUMERIC_STRING_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.OBJECT_CLASS_DESCRIPTION_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.OID_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.OTHER_MAILBOX_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.OCTET_STRING_SYNTAX, byte[].class);
        addToSyntaxMap(SchemaConstants.POSTAL_ADDRESS_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.PROTOCOL_INFORMATION_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.PRESENTATION_ADDRESS_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.PRINTABLE_STRING_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.SUBTREE_SPECIFICATION_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.SUPPLIER_INFORMATION_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.SUPPLIER_OR_CONSUMER_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.SUPPLIER_AND_CONSUMER_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.SUPPORTED_ALGORITHM_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.TELEPHONE_NUMBER_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.TELETEX_TERMINAL_IDENTIFIER_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.TELEX_NUMBER_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.UTC_TIME_SYNTAX, long.class);
        addToSyntaxMap(SchemaConstants.LDAP_SYNTAX_DESCRIPTION_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.MODIFY_RIGHTS_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.LDAP_SCHEMA_DEFINITION_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.LDAP_SCHEMA_DESCRIPTION_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.SUBSTRING_ASSERTION_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.ATTRIBUTE_CERTIFICATE_ASSERTION_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.UUID_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.CSN_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.CSN_SID_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.JAVA_BYTE_SYNTAX, byte.class);
        addToSyntaxMap(SchemaConstants.JAVA_CHAR_SYNTAX, char.class);
        addToSyntaxMap(SchemaConstants.JAVA_SHORT_SYNTAX, short.class);
        addToSyntaxMap(SchemaConstants.JAVA_LONG_SYNTAX, long.class);
        addToSyntaxMap(SchemaConstants.JAVA_INT_SYNTAX, int.class);
        addToSyntaxMap(SchemaConstants.COMPARATOR_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.NORMALIZER_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.SYNTAX_CHECKER_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.SEARCH_SCOPE_SYNTAX, String.class);
        addToSyntaxMap(SchemaConstants.DEREF_ALIAS_SYNTAX, String.class);
        addToSyntaxMap(LdapConstants.SYNTAX_AUTH_PASSWORD, String.class);
        addToSyntaxMap(LdapConstants.SYNTAX_COLLECTIVE_CONFLICT_BEHAVIOR, String.class);
        addToSyntaxMap(LdapConstants.SYNTAX_SUN_DEFINED_ACCESS_CONTROL_INFORMATION, String.class);
        addToSyntaxMap(LdapConstants.SYNTAX_NIS_NETGROUP_TRIPLE_SYNTAX, String.class);
        addToSyntaxMap(LdapConstants.SYNTAX_NIS_BOOT_PARAMETER_SYNTAX, String.class);
        addToSyntaxMap(LdapConstants.SYNTAX_AD_CASE_IGNORE_STRING_TELETEX_SYNTAX, String.class,
                AttributeInfo.Subtypes.STRING_CASE_IGNORE);
        addToSyntaxMap(LdapConstants.SYNTAX_AD_CASE_IGNORE_STRING_SYNTAX, String.class,
                AttributeInfo.Subtypes.STRING_CASE_IGNORE);
        addToSyntaxMap(LdapConstants.SYNTAX_AD_DN_WITH_STRING_SYNTAX, String.class);
        addToSyntaxMap(LdapConstants.SYNTAX_AD_DN_WITH_BINARY_SYNTAX, String.class);
        addToSyntaxMap(LdapConstants.SYNTAX_AD_INTEGER8_SYNTAX, long.class);
        addToSyntaxMap(LdapConstants.SYNTAX_AD_SECURITY_DESCRIPTOR_SYNTAX, byte[].class);

        // AD strangeness
        addToSyntaxMap("OctetString", byte[].class);

        // Make sure that these attributes are always resolved as string attributes
        // These are mostly root DSE attributes
        // WARNING: all attribute names must be in lowercase
        STRING_ATTRIBUTE_NAMES.add("namingcontexts");
        STRING_ATTRIBUTE_NAMES.add("defaultnamingcontext");
        STRING_ATTRIBUTE_NAMES.add("schemanamingcontext");
        STRING_ATTRIBUTE_NAMES.add("supportedcontrol");
        STRING_ATTRIBUTE_NAMES.add("configurationnamingcontext");
        STRING_ATTRIBUTE_NAMES.add("rootdomainnamingcontext");
        STRING_ATTRIBUTE_NAMES.add("supportedldapversion");
        STRING_ATTRIBUTE_NAMES.add("supportedldappolicies");
        STRING_ATTRIBUTE_NAMES.add("supportedsaslmechanisms");
        STRING_ATTRIBUTE_NAMES.add("highestcommittedusn");
        STRING_ATTRIBUTE_NAMES.add("ldapservicename");
        STRING_ATTRIBUTE_NAMES.add("supportedcapabilities");
        STRING_ATTRIBUTE_NAMES.add("issynchronized");
        STRING_ATTRIBUTE_NAMES.add("isglobalcatalogready");
        STRING_ATTRIBUTE_NAMES.add("domainfunctionality");
        STRING_ATTRIBUTE_NAMES.add("forestfunctionality");
        STRING_ATTRIBUTE_NAMES.add("domaincontrollerfunctionality");
        STRING_ATTRIBUTE_NAMES.add("currenttime");
        STRING_ATTRIBUTE_NAMES.add("dsservicename");
        STRING_ATTRIBUTE_NAMES.add(LdapConstants.ATTRIBUTE_389DS_FIRSTCHANGENUMBER.toLowerCase());
        STRING_ATTRIBUTE_NAMES.add(LdapConstants.ATTRIBUTE_389DS_LASTCHANGENUMBER.toLowerCase());

    }

}