mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Matthew Swift
08.37.2013 d89aacc0431eab3dce367e8fe9575eb8f83aa535
Checkpoint changes: clean up APIs, separate out MVCC and naming strategies.
2 files deleted
3 files added
10 files modified
1113 ■■■■■ changed files
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java 20 ●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ComplexAttributeMapper.java 29 ●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java 45 ●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ConstantAttributeMapper.java 37 ●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java 2 ●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java 33 ●●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java 162 ●●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/MVCCStrategy.java 60 ●●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/NameStrategy.java 95 ●●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java 71 ●●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java 321 ●●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java 96 ●●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubContainerAttributeMapper.java 73 ●●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java 15 ●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java 54 ●●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java
@@ -30,7 +30,15 @@
 * An attribute mapper is responsible for converting JSON values to and from
 * LDAP attributes.
 */
public interface AttributeMapper {
public abstract class AttributeMapper {
    /*
     * This interface is an abstract class so that methods can be made package
     * private until API is finalized.
     */
    AttributeMapper() {
        // Nothing to do.
    }
    /**
     * Adds the names of the LDAP attributes required by this attribute mapper
@@ -48,7 +56,7 @@
     *            The set into which the required LDAP attribute names should be
     *            put.
     */
    void getLDAPAttributes(Context c, JsonPointer jsonAttribute, Set<String> ldapAttributes);
    abstract void getLDAPAttributes(Context c, JsonPointer jsonAttribute, Set<String> ldapAttributes);
    /**
     * Transforms the provided REST comparison filter parameters to an LDAP
@@ -80,8 +88,8 @@
     * @param h
     *            The result handler.
     */
    void getLDAPFilter(Context c, FilterType type, JsonPointer jsonAttribute, String operator,
            Object valueAssertion, ResultHandler<Filter> h);
    abstract void getLDAPFilter(Context c, FilterType type, JsonPointer jsonAttribute,
            String operator, Object valueAssertion, ResultHandler<Filter> h);
    /**
     * Transforms attributes contained in the provided LDAP entry to JSON
@@ -99,7 +107,7 @@
     * @param h
     *            The result handler.
     */
    void toJSON(Context c, Entry e, ResultHandler<Map<String, Object>> h);
    abstract void toJSON(Context c, Entry e, ResultHandler<Map<String, Object>> h);
    /**
     * Transforms JSON content in the provided JSON value to LDAP attributes,
@@ -116,7 +124,7 @@
     * @param h
     *            The result handler.
     */
    void toLDAP(Context c, JsonValue v, ResultHandler<List<Attribute>> h);
    abstract void toLDAP(Context c, JsonValue v, ResultHandler<List<Attribute>> h);
    // TODO: methods for obtaining schema information (e.g. name, description,
    // type information).
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ComplexAttributeMapper.java
@@ -34,7 +34,7 @@
 * An attribute mapper which maps a single JSON attribute to the result of
 * another attribute mapper.
 */
public class ComplexAttributeMapper implements AttributeMapper {
final class ComplexAttributeMapper extends AttributeMapper {
    private final String jsonAttributeName;
    private final AttributeMapper mapper;
@@ -50,17 +50,14 @@
     *            The mapper which should be used to provide the contents of the
     *            complex attribute.
     */
    public ComplexAttributeMapper(final String jsonAttributeName, final AttributeMapper mapper) {
    ComplexAttributeMapper(final String jsonAttributeName, final AttributeMapper mapper) {
        this.jsonAttributeName = jsonAttributeName;
        this.mapper = mapper;
        this.normalizedJsonAttributeName = toLowerCase(jsonAttributeName);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
    void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
            final Set<String> ldapAttributes) {
        if (jsonAttribute.isEmpty() || matches(jsonAttribute)) {
            final JsonPointer relativePointer = jsonAttribute.relativePointer();
@@ -68,13 +65,9 @@
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void getLDAPFilter(final Context c, final FilterType type,
            final JsonPointer jsonAttribute, final String operator, final Object valueAssertion,
            final ResultHandler<Filter> h) {
    void getLDAPFilter(final Context c, final FilterType type, final JsonPointer jsonAttribute,
            final String operator, final Object valueAssertion, final ResultHandler<Filter> h) {
        if (matches(jsonAttribute)) {
            final JsonPointer relativePointer = jsonAttribute.relativePointer();
            mapper.getLDAPFilter(c, type, relativePointer, operator, valueAssertion, h);
@@ -84,12 +77,8 @@
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void toJSON(final Context c, final Entry e,
            final ResultHandler<Map<String, Object>> h) {
    void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
        final ResultHandler<Map<String, Object>> wrapper =
                new ResultHandler<Map<String, Object>>() {
@@ -108,12 +97,8 @@
        mapper.toJSON(c, e, wrapper);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void toLDAP(final Context c, final JsonValue v,
            final ResultHandler<List<Attribute>> h) {
    void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
        // TODO Auto-generated method stub
    }
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java
@@ -19,10 +19,10 @@
import static org.forgerock.opendj.rest2ldap.Utils.transform;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -39,46 +39,27 @@
 * An attribute mapper which combines the results of a set of subordinate
 * attribute mappers into a single JSON object.
 */
public final class CompositeAttributeMapper implements AttributeMapper {
    private final List<AttributeMapper> attributeMappers = new LinkedList<AttributeMapper>();
final class CompositeAttributeMapper extends AttributeMapper {
    private final List<AttributeMapper> attributeMappers;
    /**
     * Creates a new composite attribute mapper.
     */
    public CompositeAttributeMapper() {
        // No implementation required.
    CompositeAttributeMapper(final Collection<AttributeMapper> mappers) {
        this.attributeMappers = new ArrayList<AttributeMapper>(mappers);
    }
    /**
     * Adds a subordinate attribute mapper to this composite attribute mapper.
     *
     * @param mapper
     *            The subordinate attribute mapper.
     * @return This composite attribute mapper.
     */
    public CompositeAttributeMapper addMapper(final AttributeMapper mapper) {
        attributeMappers.add(mapper);
        return this;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
    void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
            final Set<String> ldapAttributes) {
        for (final AttributeMapper attribute : attributeMappers) {
            attribute.getLDAPAttributes(c, jsonAttribute, ldapAttributes);
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void getLDAPFilter(final Context c, final FilterType type,
            final JsonPointer jsonAttribute, final String operator, final Object valueAssertion,
            final ResultHandler<Filter> h) {
    void getLDAPFilter(final Context c, final FilterType type, final JsonPointer jsonAttribute,
            final String operator, final Object valueAssertion, final ResultHandler<Filter> h) {
        final ResultHandler<Filter> handler =
                accumulate(attributeMappers.size(), transform(
                        new Function<List<Filter>, Filter, Void>() {
@@ -113,11 +94,8 @@
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
    void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
        final ResultHandler<Map<String, Object>> handler =
                accumulate(attributeMappers.size(), transform(
                        new Function<List<Map<String, Object>>, Map<String, Object>, Void>() {
@@ -141,11 +119,8 @@
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
    void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
        // TODO Auto-generated method stub
    }
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ConstantAttributeMapper.java
@@ -32,7 +32,7 @@
/**
 * An attribute mapper which maps a single JSON attribute to a fixed value.
 */
public class ConstantAttributeMapper implements AttributeMapper {
final class ConstantAttributeMapper extends AttributeMapper {
    private final String jsonAttributeName;
    private final Object jsonAttributeValue;
@@ -45,27 +45,20 @@
     * @param attributeValue
     *            The value of the simple JSON attribute.
     */
    public ConstantAttributeMapper(final String attributeName, final Object attributeValue) {
    ConstantAttributeMapper(final String attributeName, final Object attributeValue) {
        this.jsonAttributeName = attributeName;
        this.jsonAttributeValue = attributeValue;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
    void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
            final Set<String> ldapAttributes) {
        // Nothing to do.
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void getLDAPFilter(final Context c, final FilterType type,
            final JsonPointer jsonAttribute, final String operator, final Object valueAssertion,
            final ResultHandler<Filter> h) {
    void getLDAPFilter(final Context c, final FilterType type, final JsonPointer jsonAttribute,
            final String operator, final Object valueAssertion, final ResultHandler<Filter> h) {
        if (jsonAttribute.size() == 1 && jsonAttribute.get(0).equalsIgnoreCase(jsonAttributeName)) {
            final Filter filter;
            if (type == FilterType.PRESENT) {
@@ -107,26 +100,20 @@
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
    void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
        // FIXME: how do we know if the user requested it???
        h.handleResult(Collections.singletonMap(jsonAttributeName, jsonAttributeValue));
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
    void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
        // TODO Auto-generated method stub
    }
    private <T extends Comparable<T>> Filter compare(Context c, final FilterType type, final T v1,
            final T v2) {
    private <T extends Comparable<T>> Filter compare(final Context c, final FilterType type,
            final T v1, final T v2) {
        final Filter filter;
        switch (type) {
        case EQUAL_TO:
@@ -134,8 +121,7 @@
            break;
        case GREATER_THAN:
            filter =
                    v1.compareTo(v2) > 0 ? c.getConfig().trueFilter() : c.getConfig()
                            .falseFilter();
                    v1.compareTo(v2) > 0 ? c.getConfig().trueFilter() : c.getConfig().falseFilter();
            break;
        case GREATER_THAN_OR_EQUAL_TO:
            filter =
@@ -144,8 +130,7 @@
            break;
        case LESS_THAN:
            filter =
                    v1.compareTo(v2) < 0 ? c.getConfig().trueFilter() : c.getConfig()
                            .falseFilter();
                    v1.compareTo(v2) < 0 ? c.getConfig().trueFilter() : c.getConfig().falseFilter();
            break;
        case LESS_THAN_OR_EQUAL_TO:
            filter =
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java
@@ -24,7 +24,7 @@
    private final Config config;
    private final ServerContext context;
    Context(Config config, ServerContext context) {
    Context(final Config config, final ServerContext context) {
        this.config = config;
        this.context = context;
    }
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java
@@ -36,7 +36,7 @@
 * An attribute mapper that directly maps a configurable selection of attributes
 * to and from LDAP without any transformation.
 */
public final class DefaultAttributeMapper implements AttributeMapper {
final class DefaultAttributeMapper extends AttributeMapper {
    // All user attributes by default.
    private final Map<String, String> excludedAttributes = new LinkedHashMap<String, String>();
    private final Map<String, String> includedAttributes = new LinkedHashMap<String, String>();
@@ -45,7 +45,7 @@
     * Creates a new default attribute mapper which will map all user attributes
     * to JSON by default.
     */
    public DefaultAttributeMapper() {
    DefaultAttributeMapper() {
        // No implementation required.
    }
@@ -56,18 +56,15 @@
     *            The attributes to be excluded.
     * @return This attribute mapper.
     */
    public DefaultAttributeMapper excludeAttribute(final String... attributes) {
    DefaultAttributeMapper excludeAttribute(final String... attributes) {
        for (final String attribute : attributes) {
            excludedAttributes.put(toLowerCase(attribute), attribute);
        }
        return this;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
    void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
            final Set<String> ldapAttributes) {
        switch (jsonAttribute.size()) {
        case 0:
@@ -88,13 +85,9 @@
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void getLDAPFilter(final Context c, final FilterType type,
            final JsonPointer jsonAttribute, final String operator, final Object valueAssertion,
            final ResultHandler<Filter> h) {
    void getLDAPFilter(final Context c, final FilterType type, final JsonPointer jsonAttribute,
            final String operator, final Object valueAssertion, final ResultHandler<Filter> h) {
        if (jsonAttribute.size() == 1 && isIncludedAttribute(jsonAttribute.get(0))) {
            h.handleResult(toFilter(c, type, jsonAttribute.get(0), valueAssertion));
        } else {
@@ -110,18 +103,17 @@
     *            The attributes to be included.
     * @return This attribute mapper.
     */
    public DefaultAttributeMapper includeAttribute(final String... attributes) {
    DefaultAttributeMapper includeAttribute(final String... attributes) {
        for (final String attribute : attributes) {
            includedAttributes.put(toLowerCase(attribute), attribute);
        }
        return this;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
    void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
        // FIXME: this will leave out attributes which were not included in the LDAP entry. We should
        // ensure that JSON attributes are present, even if they are null or an empty array.
        final Map<String, Object> result = new LinkedHashMap<String, Object>(e.getAttributeCount());
        for (final Attribute a : e.getAllAttributes()) {
            final String name = getAttributeName(a);
@@ -132,11 +124,8 @@
        h.handleResult(result);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
    void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
        // TODO:
    }
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
@@ -49,13 +49,13 @@
import org.forgerock.json.resource.UncategorizedException;
import org.forgerock.json.resource.UpdateRequest;
import org.forgerock.opendj.ldap.AssertionFailureException;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AuthenticationException;
import org.forgerock.opendj.ldap.AuthorizationException;
import org.forgerock.opendj.ldap.Connection;
import org.forgerock.opendj.ldap.ConnectionException;
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.EntryNotFoundException;
import org.forgerock.opendj.ldap.ErrorResultException;
import org.forgerock.opendj.ldap.Filter;
@@ -64,6 +64,7 @@
import org.forgerock.opendj.ldap.SearchResultHandler;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.TimeoutResultException;
import org.forgerock.opendj.ldap.requests.AddRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.responses.Result;
@@ -74,7 +75,7 @@
 * A {@code CollectionResourceProvider} implementation which maps a JSON
 * resource collection to LDAP entries beneath a base DN.
 */
public class LDAPCollectionResourceProvider implements CollectionResourceProvider {
final class LDAPCollectionResourceProvider implements CollectionResourceProvider {
    private abstract class AbstractRequestCompletionHandler<R,
            H extends org.forgerock.opendj.ldap.ResultHandler<? super R>>
@@ -137,17 +138,11 @@
            super(connection, resultHandler);
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public final boolean handleEntry(final SearchResultEntry entry) {
            return resultHandler.handleEntry(entry);
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public final boolean handleReference(final SearchResultReference reference) {
            return resultHandler.handleReference(reference);
@@ -155,95 +150,93 @@
    }
    // FIXME: make this configurable.
    private static final String ETAG_ATTRIBUTE = "etag";
    // Dummy exception used for signalling search success.
    private static final ResourceException SUCCESS = new UncategorizedException(0, null, null);
    // FIXME: make this configurable, also allow use of DN.
    private static final String UUID_ATTRIBUTE = "entryUUID";
    private final AttributeMapper attributeMapper;
    private final DN baseDN; // TODO: support template variables.
    private final Config config;
    private final ConnectionFactory factory;
    private final MVCCStrategy mvccStrategy;
    private final NameStrategy nameStrategy;
    /**
     * Creates a new LDAP resource.
     *
     * @param baseDN
     *            The parent of all entries contained in this LDAP collection.
     * @param mapper
     *            The attribute mapper which will be used for mapping LDAP
     *            attributes to JSON attributes.
     * @param factory
     *            The LDAP connection factory which will be used for performing
     *            LDAP operations.
     * @param config
     *            Common configuration options.
     */
    public LDAPCollectionResourceProvider(final DN baseDN, final AttributeMapper mapper,
            final ConnectionFactory factory, final Config config) {
    LDAPCollectionResourceProvider(final DN baseDN, final AttributeMapper mapper,
            final ConnectionFactory factory, final Config config, final NameStrategy nameStrategy,
            final MVCCStrategy mvccStrategy) {
        this.baseDN = baseDN;
        this.attributeMapper = mapper;
        this.factory = factory;
        this.config = config;
        this.nameStrategy = nameStrategy;
        this.mvccStrategy = mvccStrategy;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void actionCollection(final ServerContext context, final ActionRequest request,
            final ResultHandler<JsonValue> handler) {
        handler.handleError(new NotSupportedException("Not yet implemented"));
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void actionInstance(final ServerContext context, final String resourceId,
            final ActionRequest request, final ResultHandler<JsonValue> handler) {
        handler.handleError(new NotSupportedException("Not yet implemented"));
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void createInstance(final ServerContext context, final CreateRequest request,
            final ResultHandler<Resource> handler) {
        handler.handleError(new NotSupportedException("Not yet implemented"));
        // We will support three use-cases:
        //
        // 1) client provided: the RDN is derived from the ID
        // 2) client provided: the RDN is derived from a JSON attribute, the ID maps to a user attribute
        // 3) server provided: the RDN is derived from a JSON attribute
        //
        // Procedure:
        //
        // 1) Generate LDAP attributes and create entry
        // 2) Apply ID mapper: create RDN from entry/ID, store ID in entry
        // 3) Create add request
        // 4) Add post read control if policy rfc
        // 5) Do add request
        // 6) If add failed then return error
        // 7) If policy is rfc then return entry
        // 8) If policy is search then read entry
        //
        final Context c = wrap(context);
        final AddRequest addRequest = Requests.newAddRequest(DN.rootDN());
        attributeMapper.toLDAP(c, request.getContent(), new ResultHandler<List<Attribute>>() {
            @Override
            public void handleError(final ResourceException error) {
                handler.handleError(error);
            }
            @Override
            public void handleResult(final List<Attribute> result) {
                for (final Attribute attribute : result) {
                    addRequest.addAttribute(attribute);
                }
                nameStrategy.setResourceId(c, baseDN, request.getNewResourceId(), addRequest);
            }
        });
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void deleteInstance(final ServerContext context, final String resourceId,
            final DeleteRequest request, final ResultHandler<Resource> handler) {
        handler.handleError(new NotSupportedException("Not yet implemented"));
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void patchInstance(final ServerContext context, final String resourceId,
            final PatchRequest request, final ResultHandler<Resource> handler) {
        handler.handleError(new NotSupportedException("Not yet implemented"));
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void queryCollection(final ServerContext context, final QueryRequest request,
            final QueryResultHandler handler) {
        final Context c = wrap(context);
        final Collection<String> ldapAttributes = getLDAPAttributes(c, request.getFieldFilters());
        // The handler which will be invoked for each LDAP search result.
        final SearchResultHandler searchHandler = new SearchResultHandler() {
@@ -263,10 +256,8 @@
                    return false;
                }
                // TODO: should the resource or the container define the ID
                // mapping?
                final String id = getIDFromEntry(entry);
                final String revision = getEtagFromEntry(entry);
                final String id = nameStrategy.getResourceId(c, entry);
                final String revision = mvccStrategy.getRevisionFromEntry(c, entry);
                final ResultHandler<Map<String, Object>> mapHandler =
                        new ResultHandler<Map<String, Object>>() {
                            @Override
@@ -341,7 +332,6 @@
                if (ldapFilter == null || ldapFilter == c.getConfig().falseFilter()) {
                    handler.handleResult(new QueryResult());
                } else {
                    final String[] tmp = getSearchAttributes(ldapAttributes);
                    final ConnectionCompletionHandler<Result> outerHandler =
                            new ConnectionCompletionHandler<Result>(searchHandler) {
@@ -350,9 +340,12 @@
                                    final SearchRequestCompletionHandler innerHandler =
                                            new SearchRequestCompletionHandler(connection,
                                                    searchHandler);
                                    final String[] attributes =
                                            getLDAPAttributes(c, request.getFieldFilters());
                                    final SearchRequest request =
                                            Requests.newSearchRequest(baseDN,
                                                    SearchScope.SINGLE_LEVEL, ldapFilter, tmp);
                                            Requests.newSearchRequest(getBaseDN(c),
                                                    SearchScope.SINGLE_LEVEL, ldapFilter,
                                                    attributes);
                                    connection.searchAsync(request, null, innerHandler);
                                }
@@ -366,15 +359,10 @@
        getLDAPFilter(c, request.getQueryFilter(), filterHandler);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void readInstance(final ServerContext context, final String resourceId,
            final ReadRequest request, final ResultHandler<Resource> handler) {
        final Context c = wrap(context);
        final Collection<String> ldapAttributes = getLDAPAttributes(c, request.getFieldFilters());
        final String[] tmp = getSearchAttributes(ldapAttributes);
        // The handler which will be invoked for the LDAP search result.
        final org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry> searchHandler =
@@ -386,7 +374,7 @@
                    @Override
                    public void handleResult(final SearchResultEntry entry) {
                        final String revision = getEtagFromEntry(entry);
                        final String revision = mvccStrategy.getRevisionFromEntry(c, entry);
                        final ResultHandler<Map<String, Object>> mapHandler =
                                new ResultHandler<Map<String, Object>>() {
                                    @Override
@@ -415,9 +403,10 @@
                        final RequestCompletionHandler<SearchResultEntry> innerHandler =
                                new RequestCompletionHandler<SearchResultEntry>(connection,
                                        searchHandler);
                        final String[] attributes = getLDAPAttributes(c, request.getFieldFilters());
                        final SearchRequest request =
                                Requests.newSearchRequest(baseDN, SearchScope.SINGLE_LEVEL, Filter
                                        .equality(UUID_ATTRIBUTE, resourceId), tmp);
                                nameStrategy.createSearchRequest(c, getBaseDN(c), resourceId)
                                        .addAttribute(attributes);
                        connection.searchSingleEntryAsync(request, innerHandler);
                    }
@@ -426,9 +415,6 @@
        factory.getConnectionAsync(outerHandler);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void updateInstance(final ServerContext context, final String resourceId,
            final UpdateRequest request, final ResultHandler<Resource> handler) {
@@ -466,26 +452,8 @@
        return ResourceException.getException(resourceResultCode, null, error.getMessage(), error);
    }
    /**
     * Returns the ETag for the provided entry.
     *
     * @param entry
     *            The entry.
     * @return The ETag.
     */
    private String getEtagFromEntry(final Entry entry) {
        return entry.parseAttribute(ETAG_ATTRIBUTE).asString();
    }
    /**
     * Returns the resource ID for the provided entry.
     *
     * @param entry
     *            The entry.
     * @return The resource ID.
     */
    private String getIDFromEntry(final Entry entry) {
        return entry.parseAttribute(UUID_ATTRIBUTE).asString();
    private DN getBaseDN(final Context context) {
        return baseDN;
    }
    /**
@@ -497,8 +465,9 @@
     * @return The set of LDAP attributes associated with the resource
     *         attributes.
     */
    private Collection<String> getLDAPAttributes(final Context c,
    private String[] getLDAPAttributes(final Context c,
            final Collection<JsonPointer> requestedAttributes) {
        // Get all the LDAP attributes required by the attribute mappers.
        final Set<String> requestedLDAPAttributes;
        if (requestedAttributes.isEmpty()) {
            // Full read.
@@ -511,7 +480,11 @@
                attributeMapper.getLDAPAttributes(c, requestedAttribute, requestedLDAPAttributes);
            }
        }
        return requestedLDAPAttributes;
        // Get the LDAP attributes required by the Etag and name stategies.
        nameStrategy.getLDAPAttributes(c, requestedLDAPAttributes);
        mvccStrategy.getLDAPAttributes(c, requestedLDAPAttributes);
        return requestedLDAPAttributes.toArray(new String[requestedLDAPAttributes.size()]);
    }
    private void getLDAPFilter(final Context c, final QueryFilter queryFilter,
@@ -700,15 +673,6 @@
        queryFilter.accept(visitor, h);
    }
    private String[] getSearchAttributes(final Collection<String> attributes) {
        // FIXME: who is responsible for adding the UUID and etag attributes to
        // this search?
        final String[] tmp = attributes.toArray(new String[attributes.size() + 2]);
        tmp[tmp.length - 2] = UUID_ATTRIBUTE;
        tmp[tmp.length - 1] = ETAG_ATTRIBUTE;
        return tmp;
    }
    private Context wrap(final ServerContext context) {
        return new Context(config, context);
    }
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/MVCCStrategy.java
New file
@@ -0,0 +1,60 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2013 ForgeRock AS.
 */
package org.forgerock.opendj.rest2ldap;
import java.util.Set;
import org.forgerock.opendj.ldap.Entry;
/**
 * A multi-version concurrency control strategy is responsible for ensuring that
 * clients can perform atomic updates to LDAP resources.
 */
public abstract class MVCCStrategy {
    /*
     * This interface is an abstract class so that methods can be made package
     * private until API is finalized.
     */
    MVCCStrategy() {
        // Nothing to do.
    }
    /**
     * Retrieves the revision value (etag) from the provided LDAP entry.
     *
     * @param c
     *            The context.
     * @param entry
     *            The LDAP entry.
     * @return The revision value.
     */
    abstract String getRevisionFromEntry(Context c, Entry entry);
    /**
     * Adds the name of any LDAP attribute required by this MVCC strategy to the
     * provided set.
     *
     * @param c
     *            The context.
     * @param ldapAttributes
     *            The set into which any required LDAP attribute name should be
     *            put.
     */
    abstract void getLDAPAttributes(Context c, Set<String> ldapAttributes);
}
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/NameStrategy.java
New file
@@ -0,0 +1,95 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2013 ForgeRock AS.
 */
package org.forgerock.opendj.rest2ldap;
import java.util.Set;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.requests.SearchRequest;
/**
 * A name strategy is responsible for naming REST resources and LDAP entries.
 */
public abstract class NameStrategy {
    /*
     * This interface is an abstract class so that methods can be made package
     * private until API is finalized.
     */
    NameStrategy() {
        // Nothing to do.
    }
    /**
     * Returns a search request which can be used to obtain the specified REST
     * resource.
     *
     * @param c
     *            The context.
     * @param baseDN
     *            The search base DN.
     * @param resourceId
     *            The resource ID.
     * @return A search request which can be used to obtain the specified REST
     *         resource.
     */
    abstract SearchRequest createSearchRequest(Context c, DN baseDN, String resourceId);
    /**
     * Adds the name of any LDAP attribute required by this name strategy to the
     * provided set.
     *
     * @param c
     *            The context.
     * @param ldapAttributes
     *            The set into which any required LDAP attribute name should be
     *            put.
     */
    abstract void getLDAPAttributes(Context c, Set<String> ldapAttributes);
    /**
     * Retrieves the resource ID from the provided LDAP entry. Implementations
     * may use the entry DN as well as any attributes in order to determine the
     * resource ID.
     *
     * @param c
     *            The context.
     * @param entry
     *            The LDAP entry from which the resource ID should be obtained.
     * @return The resource ID.
     */
    abstract String getResourceId(Context c, Entry entry);
    /**
     * Sets the resource ID in the provided LDAP entry. Implementations are
     * responsible for setting the entry DN as well as any attributes associated
     * with the resource ID.
     *
     * @param c
     *            The context.
     * @param baseDN
     *            The baseDN to use when constructing the entry's DN.
     * @param resourceId
     *            The resource ID.
     * @param entry
     *            The LDAP entry whose DN and resource ID attributes are to be
     *            set.
     */
    abstract void setResourceId(Context c, DN baseDN, String resourceId, Entry entry);
}
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
File was deleted
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
New file
@@ -0,0 +1,321 @@
/*
 * The contents of this file are subject to the terms of the Common Development and
 * Distribution License (the License). You may not use this file except in compliance with the
 * License.
 *
 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
 * specific language governing permission and limitations under the License.
 *
 * When distributing Covered Software, include this CDDL Header Notice in each file and include
 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
 * Header, with the fields enclosed by brackets [] replaced by your own identifying
 * information: "Portions copyright [year] [name of copyright owner]".
 *
 * Copyright 2013 ForgeRock AS.
 */
package org.forgerock.opendj.rest2ldap;
import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest;
import static org.forgerock.opendj.ldap.schema.CoreSchema.getEntryUUIDAttributeType;
import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.forgerock.json.resource.CollectionResourceProvider;
import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.Filter;
import org.forgerock.opendj.ldap.LinkedAttribute;
import org.forgerock.opendj.ldap.RDN;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.schema.AttributeType;
import org.forgerock.opendj.ldap.schema.Schema;
/**
 * Provides core factory methods and builders for constructing LDAP resource
 * collections.
 */
public final class Rest2LDAP {
    // @Checkstyle:off
    /**
     * A builder for incrementally constructing LDAP resource collections.
     */
    public static final class Builder {
        private DN baseDN; // TODO: support template variables.
        private Config config = Config.defaultConfig();
        private ConnectionFactory factory;
        private final List<AttributeMapper> mappers = new LinkedList<AttributeMapper>();
        private MVCCStrategy mvccStrategy = mvccUsingEtag();
        private NameStrategy nameStrategy = nameByEntryUUID("uid");
        Builder() {
            // No implementation required.
        }
        public Builder baseDN(final DN dn) {
            ensureNotNull(dn);
            this.baseDN = dn;
            return this;
        }
        public Builder baseDN(final String dn) {
            ensureNotNull(dn);
            this.baseDN = DN.valueOf(dn);
            return this;
        }
        public CollectionResourceProvider build() {
            ensureNotNull(factory);
            ensureNotNull(baseDN);
            if (mappers.isEmpty()) {
                throw new IllegalStateException("No mappings provided");
            }
            return new LDAPCollectionResourceProvider(baseDN, mapOf(mappers), factory, config,
                    nameStrategy, mvccStrategy);
        }
        public Builder config(final Config config) {
            ensureNotNull(config);
            this.config = config;
            return this;
        }
        public Builder factory(final ConnectionFactory factory) {
            ensureNotNull(factory);
            this.factory = factory;
            return this;
        }
        public Builder map(final AttributeMapper... mappers) {
            ensureNotNull(mappers);
            this.mappers.addAll(Arrays.asList(mappers));
            return this;
        }
        public Builder map(final Collection<AttributeMapper> mappers) {
            ensureNotNull(mappers);
            this.mappers.addAll(mappers);
            return this;
        }
        public Builder with(final MVCCStrategy strategy) {
            ensureNotNull(strategy);
            this.mvccStrategy = strategy;
            return this;
        }
        public Builder with(final NameStrategy strategy) {
            ensureNotNull(strategy);
            this.nameStrategy = strategy;
            return this;
        }
    }
    private static final class AttributeMVCCStrategy extends MVCCStrategy {
        private final AttributeDescription ldapAttribute;
        private AttributeMVCCStrategy(final AttributeDescription ldapAttribute) {
            this.ldapAttribute = ldapAttribute;
        }
        @Override
        String getRevisionFromEntry(final Context c, final Entry entry) {
            return entry.parseAttribute(ldapAttribute).asString();
        }
        @Override
        void getLDAPAttributes(final Context c, final Set<String> ldapAttributes) {
            ldapAttributes.add(ldapAttribute.toString());
        }
    }
    private static final class AttributeNameStrategy extends NameStrategy {
        private final AttributeDescription dnAttribute;
        private final AttributeDescription idAttribute;
        private final boolean isServerProvided;
        private AttributeNameStrategy(final AttributeType dnAttribute,
                final AttributeDescription idAttribute, final boolean isServerProvided) {
            this.dnAttribute = AttributeDescription.create(dnAttribute);
            if (dnAttribute.equals(idAttribute)) {
                throw new IllegalArgumentException("DN and ID attributes must be different");
            }
            this.idAttribute = ensureNotNull(idAttribute);
            this.isServerProvided = isServerProvided;
        }
        @Override
        SearchRequest createSearchRequest(final Context c, final DN baseDN, final String resourceId) {
            return newSearchRequest(baseDN, SearchScope.SINGLE_LEVEL, Filter.equality(idAttribute
                    .toString(), resourceId));
        }
        @Override
        void getLDAPAttributes(final Context c, final Set<String> ldapAttributes) {
            ldapAttributes.add(idAttribute.toString());
        }
        @Override
        String getResourceId(final Context c, final Entry entry) {
            return entry.parseAttribute(idAttribute).asString();
        }
        @Override
        void setResourceId(final Context c, final DN baseDN, final String resourceId,
                final Entry entry) {
            if (!isServerProvided) {
                entry.addAttribute(new LinkedAttribute(idAttribute, ByteString.valueOf(resourceId)));
            }
            final String rdnValue = entry.parseAttribute(dnAttribute).asString();
            final RDN rdn = new RDN(dnAttribute.getAttributeType(), rdnValue);
            entry.setName(baseDN.child(rdn));
        }
    }
    private static final class DNNameStrategy extends NameStrategy {
        private final AttributeDescription attribute;
        private DNNameStrategy(final AttributeType attribute) {
            this.attribute = AttributeDescription.create(attribute);
        }
        @Override
        SearchRequest createSearchRequest(final Context c, final DN baseDN, final String resourceId) {
            return newSearchRequest(baseDN.child(rdn(resourceId)), SearchScope.BASE_OBJECT, c
                    .getConfig().trueFilter());
        }
        @Override
        void getLDAPAttributes(final Context c, final Set<String> ldapAttributes) {
            ldapAttributes.add(attribute.toString());
        }
        @Override
        String getResourceId(final Context c, final Entry entry) {
            return entry.parseAttribute(attribute).asString();
        }
        @Override
        void setResourceId(final Context c, final DN baseDN, final String resourceId,
                final Entry entry) {
            entry.setName(baseDN.child(rdn(resourceId)));
            entry.addAttribute(new LinkedAttribute(attribute, ByteString.valueOf(resourceId)));
        }
        private RDN rdn(final String resourceId) {
            return new RDN(attribute.getAttributeType(), resourceId);
        }
    }
    public static SimpleAttributeMapper map(final AttributeDescription attribute) {
        return map(attribute.toString(), attribute);
    }
    public static SimpleAttributeMapper map(final String attribute) {
        return map(attribute, attribute);
    }
    public static SimpleAttributeMapper map(final String jsonAttribute,
            final AttributeDescription ldapAttribute) {
        return new SimpleAttributeMapper(jsonAttribute, ldapAttribute);
    }
    public static SimpleAttributeMapper map(final String jsonAttribute, final String ldapAttribute) {
        return map(jsonAttribute, AttributeDescription.valueOf(ldapAttribute));
    }
    public static AttributeMapper mapAllExcept(final String... attributes) {
        return new DefaultAttributeMapper().excludeAttribute(attributes);
    }
    public static AttributeMapper mapAllOf(final String... attributes) {
        return new DefaultAttributeMapper().includeAttribute(attributes);
    }
    public static AttributeMapper mapComplex(final String jsonAttribute,
            final AttributeMapper... mappers) {
        return mapComplex(jsonAttribute, Arrays.asList(mappers));
    }
    public static AttributeMapper mapComplex(final String jsonAttribute,
            final Collection<AttributeMapper> mappers) {
        return new ComplexAttributeMapper(jsonAttribute, mapOf(mappers));
    }
    public static AttributeMapper mapConstant(final String attribute, final Object attributeValue) {
        return new ConstantAttributeMapper(attribute, attributeValue);
    }
    public static MVCCStrategy mvccUsingAttribute(final AttributeDescription attribute) {
        return new AttributeMVCCStrategy(attribute);
    }
    public static MVCCStrategy mvccUsingAttribute(final String attribute) {
        return mvccUsingAttribute(AttributeDescription.valueOf(attribute));
    }
    public static MVCCStrategy mvccUsingEtag() {
        return mvccUsingAttribute("etag");
    }
    public static NameStrategy nameByClient(final AttributeType dnAttribute,
            final AttributeDescription idAttribute) {
        return new AttributeNameStrategy(dnAttribute, idAttribute, false);
    }
    public static NameStrategy nameByClient(final String dnAttribute, final String idAttribute) {
        return nameByClient(Schema.getDefaultSchema().getAttributeType(dnAttribute),
                AttributeDescription.valueOf(idAttribute));
    }
    public static NameStrategy nameByDN(final AttributeType attribute) {
        return new DNNameStrategy(attribute);
    }
    public static NameStrategy nameByDN(final String attribute) {
        return nameByDN(Schema.getDefaultSchema().getAttributeType(attribute));
    }
    public static NameStrategy nameByEntryUUID(final AttributeType dnAttribute) {
        return nameByServer(dnAttribute, AttributeDescription.create(getEntryUUIDAttributeType()));
    }
    public static NameStrategy nameByEntryUUID(final String dnAttribute) {
        return nameByEntryUUID(Schema.getDefaultSchema().getAttributeType(dnAttribute));
    }
    public static NameStrategy nameByServer(final AttributeType dnAttribute,
            final AttributeDescription idAttribute) {
        return new AttributeNameStrategy(dnAttribute, idAttribute, true);
    }
    public static NameStrategy nameByServer(final String dnAttribute, final String idAttribute) {
        return nameByServer(Schema.getDefaultSchema().getAttributeType(dnAttribute),
                AttributeDescription.valueOf(idAttribute));
    }
    public static Builder collection() {
        return new Builder();
    }
    private static AttributeMapper mapOf(final Collection<AttributeMapper> mappers) {
        return new CompositeAttributeMapper(mappers);
    }
    private Rest2LDAP() {
        // Prevent instantiation.
    }
}
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
@@ -19,6 +19,7 @@
import static org.forgerock.opendj.rest2ldap.Utils.toFilter;
import static org.forgerock.opendj.rest2ldap.Utils.toLowerCase;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -28,6 +29,7 @@
import org.forgerock.json.fluent.JsonValue;
import org.forgerock.json.resource.ResultHandler;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.ldap.Filter;
@@ -38,38 +40,29 @@
 * An attribute mapper which maps a single JSON attribute to a single LDAP
 * attribute.
 */
public class SimpleAttributeMapper implements AttributeMapper {
public final class SimpleAttributeMapper extends AttributeMapper {
    private Function<ByteString, ?, Void> decoder = null;
    private Object defaultValue = null;
    private Collection<Object> defaultValues = Collections.emptySet();
    private boolean forceSingleValued = false;
    // private boolean isReadOnly = false;
    private final String jsonAttributeName;
    private final String ldapAttributeName;
    private final AttributeDescription ldapAttributeName;
    private final String normalizedJsonAttributeName;
    /**
     * Creates a new simple attribute mapper which maps a single LDAP attribute
     * to an entry.
     *
     * @param attributeName
     *            The name of the simple JSON and LDAP attribute.
     */
    public SimpleAttributeMapper(final String attributeName) {
        this(attributeName, attributeName);
    }
    /**
     * Creates a new simple attribute mapper which maps a single LDAP attribute
     * to an entry.
     *
     * @param jsonAttributeName
     *            The name of the simple JSON attribute.
     * @param ldapAttributeName
     *            The name of the LDAP attribute.
     */
    public SimpleAttributeMapper(final String jsonAttributeName, final String ldapAttributeName) {
    SimpleAttributeMapper(final String jsonAttributeName,
            final AttributeDescription ldapAttributeName) {
        this.jsonAttributeName = jsonAttributeName;
        this.ldapAttributeName = ldapAttributeName;
        this.normalizedJsonAttributeName = toLowerCase(jsonAttributeName);
@@ -98,36 +91,12 @@
     */
    public SimpleAttributeMapper defaultJSONValue(final Object defaultValue) {
        this.defaultValue = defaultValue;
        this.defaultValues =
                defaultValue != null ? Collections.singleton(defaultValue) : Collections.emptySet();
        return this;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
            final Set<String> ldapAttributes) {
        if (jsonAttribute.isEmpty() || matches(jsonAttribute)) {
            ldapAttributes.add(ldapAttributeName);
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void getLDAPFilter(final Context c, final FilterType type,
            final JsonPointer jsonAttribute, final String operator, final Object valueAssertion,
            final ResultHandler<Filter> h) {
        if (matches(jsonAttribute)) {
            h.handleResult(toFilter(c, type, ldapAttributeName, valueAssertion));
        } else {
            // This attribute mapper cannot handle the provided filter component.
            h.handleResult(null);
        }
    }
    /**
     * Prevents the LDAP attribute from being updated.
     *
     * @param readOnly
@@ -154,32 +123,41 @@
        return this;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
        final Attribute a = e.getAttribute(ldapAttributeName);
        if (a != null) {
            final Function<ByteString, ?, Void> f =
                    decoder == null ? Functions.fixedFunction(byteStringToJson(), a) : decoder;
            final Object value;
            if (forceSingleValued || a.getAttributeDescription().getAttributeType().isSingleValue()) {
                value = a.parse().as(f, defaultValue);
            } else {
                value = a.parse().asSetOf(f, defaultValue);
            }
            h.handleResult(Collections.singletonMap(jsonAttributeName, value));
    void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
            final Set<String> ldapAttributes) {
        if (jsonAttribute.isEmpty() || matches(jsonAttribute)) {
            ldapAttributes.add(ldapAttributeName.toString());
        }
    }
    @Override
    void getLDAPFilter(final Context c, final FilterType type, final JsonPointer jsonAttribute,
            final String operator, final Object valueAssertion, final ResultHandler<Filter> h) {
        if (matches(jsonAttribute)) {
            h.handleResult(toFilter(c, type, ldapAttributeName.toString(), valueAssertion));
        } else {
            // This attribute mapper cannot handle the provided filter component.
            h.handleResult(null);
        }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
    void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
        final Function<ByteString, ?, Void> f =
                decoder == null ? Functions.fixedFunction(byteStringToJson(), ldapAttributeName)
                        : decoder;
        final Object value;
        if (forceSingleValued || ldapAttributeName.getAttributeType().isSingleValue()) {
            value = e.parseAttribute(ldapAttributeName).as(f, defaultValue);
        } else {
            value = e.parseAttribute(ldapAttributeName).asSetOf(f, defaultValues);
        }
        h.handleResult(Collections.singletonMap(jsonAttributeName, value));
    }
    @Override
    void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
        // TODO Auto-generated method stub
    }
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubContainerAttributeMapper.java
File was deleted
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
@@ -31,6 +31,7 @@
import org.forgerock.json.resource.ResourceException;
import org.forgerock.json.resource.ResultHandler;
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.Filter;
import org.forgerock.opendj.ldap.Function;
@@ -85,12 +86,11 @@
    }
    // @Checkstyle:off
    private static final Function<ByteString, Object, Attribute> BYTESTRING_TO_JSON =
            new Function<ByteString, Object, Attribute>() {
    private static final Function<ByteString, Object, AttributeDescription> BYTESTRING_TO_JSON =
            new Function<ByteString, Object, AttributeDescription>() {
                @Override
                public Object apply(final ByteString value, final Attribute a) {
                    final Syntax syntax =
                            a.getAttributeDescription().getAttributeType().getSyntax();
                public Object apply(final ByteString value, final AttributeDescription ad) {
                    final Syntax syntax = ad.getAttributeType().getSyntax();
                    if (syntax.equals(getBooleanSyntax())) {
                        return Functions.byteStringToBoolean().apply(value, null);
                    } else if (syntax.equals(getIntegerSyntax())) {
@@ -112,7 +112,8 @@
    }
    static Object attributeToJson(final Attribute a) {
        final Function<ByteString, Object, Void> f = Functions.fixedFunction(BYTESTRING_TO_JSON, a);
        final Function<ByteString, Object, Void> f =
                Functions.fixedFunction(BYTESTRING_TO_JSON, a.getAttributeDescription());
        final boolean isSingleValued =
                a.getAttributeDescription().getAttributeType().isSingleValue();
        return isSingleValued ? a.parse().as(f) : asList(a.parse().asSetOf(f));
@@ -120,7 +121,7 @@
    // @Checkstyle:on
    static Function<ByteString, Object, Attribute> byteStringToJson() {
    static Function<ByteString, Object, AttributeDescription> byteStringToJson() {
        return BYTESTRING_TO_JSON;
    }
opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
@@ -18,13 +18,17 @@
import static org.forgerock.json.resource.Resources.newInternalConnectionFactory;
import static org.forgerock.opendj.ldap.Connections.newAuthenticatedConnectionFactory;
import static org.forgerock.opendj.rest2ldap.Rest2LDAP.collection;
import static org.forgerock.opendj.rest2ldap.Rest2LDAP.map;
import static org.forgerock.opendj.rest2ldap.Rest2LDAP.mapAllOf;
import static org.forgerock.opendj.rest2ldap.Rest2LDAP.mapComplex;
import java.util.logging.Logger;
import org.forgerock.json.resource.CollectionResourceProvider;
import org.forgerock.json.resource.Router;
import org.forgerock.json.resource.servlet.HttpServlet;
import org.forgerock.opendj.ldap.ConnectionFactory;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.Functions;
import org.forgerock.opendj.ldap.LDAPConnectionFactory;
import org.forgerock.opendj.ldap.requests.Requests;
@@ -55,39 +59,25 @@
                        Requests.newSimpleBindRequest("cn=directory manager", "password"
                                .toCharArray()));
        // Create user resource.
        final AttributeMapper userMapper =
                new CompositeAttributeMapper().addMapper(
                        new SimpleAttributeMapper("id", "entryUUID").singleValued(true)).addMapper(
                        new DefaultAttributeMapper().includeAttribute("uid", "isMemberOf",
                                "modifyTimestamp")).addMapper(
                        new ComplexAttributeMapper("name", new DefaultAttributeMapper()
                                .includeAttribute("cn", "sn", "givenName"))).addMapper(
                        new ComplexAttributeMapper("contactInformation",
                                new CompositeAttributeMapper().addMapper(
                                        new SimpleAttributeMapper("telephoneNumber").decoder(
                                                Functions.byteStringToString()).singleValued(true))
                                        .addMapper(
                                                new SimpleAttributeMapper("emailAddress", "mail")
                                                        .singleValued(true))));
        final LDAPCollectionResourceProvider userResource =
                new LDAPCollectionResourceProvider(DN.valueOf("ou=people,dc=example,dc=com"),
                        userMapper, ldapFactory, Config.defaultConfig());
        // Create group resource.
        final AttributeMapper groupMapper =
                new DefaultAttributeMapper().includeAttribute("cn", "ou", "description",
                        "uniquemember");
        final LDAPCollectionResourceProvider groupResource =
                new LDAPCollectionResourceProvider(DN.valueOf("ou=groups,dc=example,dc=com"),
                        groupMapper, ldapFactory, Config.defaultConfig());
        // Create the router.
        final Router router = new Router();
        router.addRoute("/users", userResource);
        router.addRoute("/groups", groupResource);
        // Create user resource.
        CollectionResourceProvider users =
                collection().factory(ldapFactory).baseDN("ou=people,dc=example,dc=com").map(
                        map("id", "entryUUID").singleValued(true),
                        mapAllOf("uid", "isMemberOf", "modifyTimestamp"),
                        mapComplex("name", mapAllOf("cn", "sn", "givenName")),
                        mapComplex("contactInformation", map("telephoneNumber").decoder(
                                Functions.byteStringToString()).singleValued(true), map(
                                "emailAddress", "mail").singleValued(true))).build();
        router.addRoute("/users", users);
        // Create group resource.
        CollectionResourceProvider groups =
                collection().factory(ldapFactory).baseDN("ou=groups,dc=example,dc=com").map(
                        mapAllOf("cn", "ou", "description", "uniquemember")).build();
        router.addRoute("/groups", groups);
        final org.forgerock.json.resource.ConnectionFactory resourceFactory =
                newInternalConnectionFactory(router);