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

Matthew Swift
01.57.2013 16869da731710f4c3754c7b229fa7346211a25a4
Pull EntryContainer into LDAPCollectionResourceProvider until layering is better understood.
1 files deleted
6 files modified
788 ■■■■■ changed files
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java 8 ●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java 197 ●●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ConstantAttributeMapper.java 32 ●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/EntryContainer.java 238 ●●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java 247 ●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java 2 ●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java 64 ●●●● patch | view | raw | blame | history
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java
@@ -91,10 +91,10 @@
                                    if (f == null) {
                                        // No mapping so remove.
                                        i.remove();
                                    } else if (f == c.getConfig().getFalseFilter()) {
                                        return c.getConfig().getFalseFilter();
                                    } else if (f == c.getConfig().getTrueFilter()) {
                                        return c.getConfig().getTrueFilter();
                                    } else if (f == c.getConfig().falseFilter()) {
                                        return c.getConfig().falseFilter();
                                    } else if (f == c.getConfig().trueFilter()) {
                                        return c.getConfig().trueFilter();
                                    }
                                }
                                switch (value.size()) {
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java
@@ -15,6 +15,9 @@
 */
package org.forgerock.opendj.rest2ldap;
import static org.forgerock.opendj.rest2ldap.Config.ReadOnUpdatePolicy.USE_READ_ENTRY_CONTROLS;
import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull;
import org.forgerock.opendj.ldap.Filter;
/**
@@ -22,24 +25,200 @@
 */
public final class Config {
    private final Filter trueFilter = Filter.objectClassPresent();
    private final Filter falseFilter = Filter.present("1.1");
    /**
     * An interface for incrementally constructing common configuration options.
     */
    public static final class Builder {
        private Filter falseFilter;
        private ReadOnUpdatePolicy readOnUpdatePolicy;
        private Filter trueFilter;
        private Builder() {
            // Nothing to do.
        }
        /**
         * Returns a new configuration based on the current state of this
         * builder.
         *
         * @return A new configuration based on the current state of this
         *         builder.
         */
        public Config build() {
            return new Config(trueFilter, falseFilter, readOnUpdatePolicy);
        }
        /**
         * Sets the absolute false filter which should be used when querying the
         * LDAP server.
         *
         * @param filter
         *            The absolute false filter.
         * @return A reference to this builder.
         */
        public Builder falseFilter(final Filter filter) {
            this.trueFilter = ensureNotNull(filter);
            return this;
        }
        /**
         * Sets the policy which should be used in order to read an entry before
         * it is deleted, or after it is added or modified.
         *
         * @param policy
         *            The policy which should be used in order to read an entry
         *            before it is deleted, or after it is added or modified.
         * @return A reference to this builder.
         */
        public Builder readOnUpdatePolicy(final ReadOnUpdatePolicy policy) {
            this.readOnUpdatePolicy = ensureNotNull(policy);
            return this;
        }
        /**
         * Sets the absolute true filter which should be used when querying the
         * LDAP server.
         *
         * @param filter
         *            The absolute true filter.
         * @return A reference to this builder.
         */
        public Builder trueFilter(final Filter filter) {
            this.trueFilter = ensureNotNull(filter);
            return this;
        }
    };
    /**
     * Returns the absolute true filter.
     *
     * @return The absolute true filter.
     * The policy which should be used in order to read an entry before it is
     * deleted, or after it is added or modified.
     */
    public Filter getTrueFilter() {
        return trueFilter;
    public static enum ReadOnUpdatePolicy {
        /**
         * The LDAP entry will not be read when an update is performed. More
         * specifically, the REST resource will not be returned as part of a
         * create, delete, patch, or update request.
         */
        DISABLED,
        /**
         * The LDAP entry will be read atomically using the RFC 4527 read-entry
         * controls. More specifically, the REST resource will be returned as
         * part of a create, delete, patch, or update request, and it will
         * reflect the state of the resource at the time the update was
         * performed. This policy requires that the LDAP server supports RFC
         * 4527.
         */
        USE_READ_ENTRY_CONTROLS,
        /**
         * The LDAP entry will be read non-atomically using an LDAP search when
         * an update is performed. More specifically, the REST resource will be
         * returned as part of a create, delete, patch, or update request, but
         * it may not reflect the state of the resource at the time the update
         * was performed.
         */
        USE_SEARCH;
    }
    private static final Config DEFAULT = new Builder().trueFilter(Filter.objectClassPresent())
            .falseFilter(Filter.present("1.1")).readOnUpdatePolicy(USE_READ_ENTRY_CONTROLS).build();
    /**
     * Returns a new builder which can be used for incrementally constructing
     * common configuration options. The builder will initially have
     * {@link #defaultConfig() default} settings.
     *
     * @return The new builder.
     */
    public static Builder builder() {
        return builder(DEFAULT);
    }
    /**
     * Returns the absolute false filter.
     * Returns a new builder which can be used for incrementally constructing
     * common configuration options. The builder will initially have the same
     * settings as the provided configuration.
     *
     * @param config
     *            The initial settings.
     * @return The new builder.
     */
    public static Builder builder(final Config config) {
        return new Builder().trueFilter(config.trueFilter()).falseFilter(config.falseFilter())
                .readOnUpdatePolicy(config.readOnUpdatePolicy());
    }
    /**
     * Returns the default configuration having the following settings:
     * <ul>
     * <li>the absolute true filter {@code (objectClass=*)}
     * <li>the absolute false filter {@code (1.1=*)}
     * <li>the read on update policy
     * {@link ReadOnUpdatePolicy#USE_READ_ENTRY_CONTROLS}.
     * </ul>
     *
     * @return The default configuration.
     */
    public static Config defaultConfig() {
        return DEFAULT;
    }
    private final Filter falseFilter;
    private final ReadOnUpdatePolicy readOnUpdatePolicy;
    private final Filter trueFilter;
    private Config(final Filter trueFilter, final Filter falseFilter,
            final ReadOnUpdatePolicy readOnUpdatePolicy) {
        this.trueFilter = trueFilter;
        this.falseFilter = falseFilter;
        this.readOnUpdatePolicy = readOnUpdatePolicy;
    }
    /**
     * Returns the absolute false filter which should be used when querying the
     * LDAP server.
     *
     * @return The absolute false filter.
     */
    public Filter getFalseFilter() {
    public Filter falseFilter() {
        return falseFilter;
    }
    /**
     * Returns the policy which should be used in order to read an entry before
     * it is deleted, or after it is added or modified.
     *
     * @return The policy which should be used in order to read an entry before
     *         it is deleted, or after it is added or modified.
     */
    public ReadOnUpdatePolicy readOnUpdatePolicy() {
        return readOnUpdatePolicy;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        final StringBuilder builder = new StringBuilder();
        builder.append("trueFilter=");
        builder.append(trueFilter);
        builder.append(", falseFilter=");
        builder.append(falseFilter);
        builder.append(", readOnUpdatePolicy=");
        builder.append(readOnUpdatePolicy);
        return builder.toString();
    }
    /**
     * Returns the absolute true filter which should be used when querying the
     * LDAP server.
     *
     * @return The absolute true filter.
     */
    public Filter trueFilter() {
        return trueFilter;
    }
}
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ConstantAttributeMapper.java
@@ -69,20 +69,20 @@
        if (jsonAttribute.size() == 1 && jsonAttribute.get(0).equalsIgnoreCase(jsonAttributeName)) {
            final Filter filter;
            if (type == FilterType.PRESENT) {
                filter = c.getConfig().getTrueFilter();
                filter = c.getConfig().trueFilter();
            } else if (jsonAttributeValue instanceof String && valueAssertion instanceof String) {
                final String v1 = toLowerCase((String) jsonAttributeValue);
                final String v2 = toLowerCase((String) valueAssertion);
                switch (type) {
                case CONTAINS:
                    filter =
                            v1.contains(v2) ? c.getConfig().getTrueFilter() : c.getConfig()
                                    .getFalseFilter();
                            v1.contains(v2) ? c.getConfig().trueFilter() : c.getConfig()
                                    .falseFilter();
                    break;
                case STARTS_WITH:
                    filter =
                            v1.startsWith(v2) ? c.getConfig().getTrueFilter() : c.getConfig()
                                    .getFalseFilter();
                            v1.startsWith(v2) ? c.getConfig().trueFilter() : c.getConfig()
                                    .falseFilter();
                    break;
                default:
                    filter = compare(c, type, v1, v2);
@@ -98,7 +98,7 @@
                filter = compare(c, type, v1, v2);
            } else {
                // This attribute mapper is a candidate but it does not match.
                filter = c.getConfig().getFalseFilter();
                filter = c.getConfig().falseFilter();
            }
            h.handleResult(filter);
        } else {
@@ -130,30 +130,30 @@
        final Filter filter;
        switch (type) {
        case EQUAL_TO:
            filter = v1.equals(v2) ? c.getConfig().getTrueFilter() : c.getConfig().getFalseFilter();
            filter = v1.equals(v2) ? c.getConfig().trueFilter() : c.getConfig().falseFilter();
            break;
        case GREATER_THAN:
            filter =
                    v1.compareTo(v2) > 0 ? c.getConfig().getTrueFilter() : c.getConfig()
                            .getFalseFilter();
                    v1.compareTo(v2) > 0 ? c.getConfig().trueFilter() : c.getConfig()
                            .falseFilter();
            break;
        case GREATER_THAN_OR_EQUAL_TO:
            filter =
                    v1.compareTo(v2) >= 0 ? c.getConfig().getTrueFilter() : c.getConfig()
                            .getFalseFilter();
                    v1.compareTo(v2) >= 0 ? c.getConfig().trueFilter() : c.getConfig()
                            .falseFilter();
            break;
        case LESS_THAN:
            filter =
                    v1.compareTo(v2) < 0 ? c.getConfig().getTrueFilter() : c.getConfig()
                            .getFalseFilter();
                    v1.compareTo(v2) < 0 ? c.getConfig().trueFilter() : c.getConfig()
                            .falseFilter();
            break;
        case LESS_THAN_OR_EQUAL_TO:
            filter =
                    v1.compareTo(v2) <= 0 ? c.getConfig().getTrueFilter() : c.getConfig()
                            .getFalseFilter();
                    v1.compareTo(v2) <= 0 ? c.getConfig().trueFilter() : c.getConfig()
                            .falseFilter();
            break;
        default:
            filter = c.getConfig().getFalseFilter(); // Not supported.
            filter = c.getConfig().falseFilter(); // Not supported.
            break;
        }
        return filter;
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/EntryContainer.java
File was deleted
opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
@@ -51,14 +51,21 @@
import org.forgerock.opendj.ldap.AssertionFailureException;
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;
import org.forgerock.opendj.ldap.Function;
import org.forgerock.opendj.ldap.MultipleEntriesFoundException;
import org.forgerock.opendj.ldap.SearchResultHandler;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.TimeoutResultException;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.SearchRequest;
import org.forgerock.opendj.ldap.responses.Result;
import org.forgerock.opendj.ldap.responses.SearchResultEntry;
import org.forgerock.opendj.ldap.responses.SearchResultReference;
@@ -68,25 +75,120 @@
 * resource collection to LDAP entries beneath a base DN.
 */
public class LDAPCollectionResourceProvider implements CollectionResourceProvider {
    private abstract class AbstractRequestCompletionHandler<R,
            H extends org.forgerock.opendj.ldap.ResultHandler<? super R>>
            implements org.forgerock.opendj.ldap.ResultHandler<R> {
        final Connection connection;
        final H resultHandler;
        AbstractRequestCompletionHandler(final Connection connection, final H resultHandler) {
            this.connection = connection;
            this.resultHandler = resultHandler;
        }
        @Override
        public final void handleErrorResult(final ErrorResultException error) {
            connection.close();
            resultHandler.handleErrorResult(error);
        }
        @Override
        public final void handleResult(final R result) {
            connection.close();
            resultHandler.handleResult(result);
        }
    }
    private abstract class ConnectionCompletionHandler<R> implements
            org.forgerock.opendj.ldap.ResultHandler<Connection> {
        private final org.forgerock.opendj.ldap.ResultHandler<? super R> resultHandler;
        ConnectionCompletionHandler(
                final org.forgerock.opendj.ldap.ResultHandler<? super R> resultHandler) {
            this.resultHandler = resultHandler;
        }
        @Override
        public final void handleErrorResult(final ErrorResultException error) {
            resultHandler.handleErrorResult(error);
        }
        @Override
        public abstract void handleResult(Connection connection);
    }
    private final class RequestCompletionHandler<R> extends
            AbstractRequestCompletionHandler<R, org.forgerock.opendj.ldap.ResultHandler<? super R>> {
        RequestCompletionHandler(final Connection connection,
                final org.forgerock.opendj.ldap.ResultHandler<? super R> resultHandler) {
            super(connection, resultHandler);
        }
    }
    private final class SearchRequestCompletionHandler extends
            AbstractRequestCompletionHandler<Result, SearchResultHandler> implements
            SearchResultHandler {
        SearchRequestCompletionHandler(final Connection connection,
                final SearchResultHandler resultHandler) {
            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);
        }
    }
    // 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 EntryContainer entryContainer;
    private final Config config = new Config();
    private final DN baseDN; // TODO: support template variables.
    private final Config config;
    private final ConnectionFactory factory;
    /**
     * Creates a new LDAP resource.
     *
     * @param container
     *            The LDAP entry container.
     * @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 EntryContainer container,
            final AttributeMapper mapper) {
        this.entryContainer = container;
    public LDAPCollectionResourceProvider(final DN baseDN, final AttributeMapper mapper,
            final ConnectionFactory factory, final Config config) {
        this.baseDN = baseDN;
        this.attributeMapper = mapper;
        this.factory = factory;
        this.config = config;
    }
    /**
@@ -140,8 +242,10 @@
    @Override
    public void queryCollection(final ServerContext context, final QueryRequest request,
            final QueryResultHandler handler) {
        // List the entries.
        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() {
            private final AtomicInteger pendingResourceCount = new AtomicInteger();
            private final AtomicReference<ResourceException> pendingResult =
@@ -161,8 +265,8 @@
                // TODO: should the resource or the container define the ID
                // mapping?
                final String id = entryContainer.getIDFromEntry(entry);
                final String revision = entryContainer.getEtagFromEntry(entry);
                final String id = getIDFromEntry(entry);
                final String revision = getEtagFromEntry(entry);
                final ResultHandler<Map<String, Object>> mapHandler =
                        new ResultHandler<Map<String, Object>>() {
                            @Override
@@ -224,8 +328,8 @@
            }
        };
        final Collection<String> ldapAttributes = getLDAPAttributes(c, request.getFieldFilters());
        getLDAPFilter(c, request.getQueryFilter(), new ResultHandler<Filter>() {
        // The handler which will be invoked once the LDAP filter has been transformed.
        final ResultHandler<Filter> filterHandler = new ResultHandler<Filter>() {
            @Override
            public void handleError(final ResourceException error) {
                handler.handleError(error);
@@ -234,13 +338,32 @@
            @Override
            public void handleResult(final Filter ldapFilter) {
                // Avoid performing a search if the filter could not be mapped or if it will never match.
                if (ldapFilter == null || ldapFilter == c.getConfig().getFalseFilter()) {
                if (ldapFilter == null || ldapFilter == c.getConfig().falseFilter()) {
                    handler.handleResult(new QueryResult());
                } else {
                    entryContainer.listEntries(c, ldapFilter, ldapAttributes, searchHandler);
                    final String[] tmp = getSearchAttributes(ldapAttributes);
                    final ConnectionCompletionHandler<Result> outerHandler =
                            new ConnectionCompletionHandler<Result>(searchHandler) {
                                @Override
                                public void handleResult(final Connection connection) {
                                    final SearchRequestCompletionHandler innerHandler =
                                            new SearchRequestCompletionHandler(connection,
                                                    searchHandler);
                                    final SearchRequest request =
                                            Requests.newSearchRequest(baseDN,
                                                    SearchScope.SINGLE_LEVEL, ldapFilter, tmp);
                                    connection.searchAsync(request, null, innerHandler);
                                }
                            };
                    factory.getConnectionAsync(outerHandler);
                }
            }
        });
        };
        getLDAPFilter(c, request.getQueryFilter(), filterHandler);
    }
    /**
@@ -250,7 +373,10 @@
    public void readInstance(final ServerContext context, final String resourceId,
            final ReadRequest request, final ResultHandler<Resource> handler) {
        final Context c = wrap(context);
        // @Checkstyle:off
        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 =
                new org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry>() {
                    @Override
@@ -260,7 +386,7 @@
                    @Override
                    public void handleResult(final SearchResultEntry entry) {
                        final String revision = entryContainer.getEtagFromEntry(entry);
                        final String revision = getEtagFromEntry(entry);
                        final ResultHandler<Map<String, Object>> mapHandler =
                                new ResultHandler<Map<String, Object>>() {
                                    @Override
@@ -279,9 +405,25 @@
                        attributeMapper.toJSON(c, entry, mapHandler);
                    }
                };
        // @Checkstyle:on
        final Collection<String> ldapAttributes = getLDAPAttributes(c, request.getFieldFilters());
        entryContainer.readEntry(c, resourceId, ldapAttributes, searchHandler);
        // The handler which will be invoked
        final ConnectionCompletionHandler<SearchResultEntry> outerHandler =
                new ConnectionCompletionHandler<SearchResultEntry>(searchHandler) {
                    @Override
                    public void handleResult(final Connection connection) {
                        final RequestCompletionHandler<SearchResultEntry> innerHandler =
                                new RequestCompletionHandler<SearchResultEntry>(connection,
                                        searchHandler);
                        final SearchRequest request =
                                Requests.newSearchRequest(baseDN, SearchScope.SINGLE_LEVEL, Filter
                                        .equality(UUID_ATTRIBUTE, resourceId), tmp);
                        connection.searchSingleEntryAsync(request, innerHandler);
                    }
                };
        factory.getConnectionAsync(outerHandler);
    }
    /**
@@ -325,6 +467,28 @@
    }
    /**
     * 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();
    }
    /**
     * Determines the set of LDAP attributes to request in an LDAP read (search,
     * post-read), based on the provided list of JSON pointers.
     *
@@ -369,16 +533,16 @@
                                                    final Filter f = i.next();
                                                    if (f == null) {
                                                        // Filter component did not match any attribute mappers.
                                                        return c.getConfig().getFalseFilter();
                                                    } else if (f == c.getConfig().getFalseFilter()) {
                                                        return c.getConfig().getFalseFilter();
                                                    } else if (f == c.getConfig().getTrueFilter()) {
                                                        return c.getConfig().falseFilter();
                                                    } else if (f == c.getConfig().falseFilter()) {
                                                        return c.getConfig().falseFilter();
                                                    } else if (f == c.getConfig().trueFilter()) {
                                                        i.remove();
                                                    }
                                                }
                                                switch (value.size()) {
                                                case 0:
                                                    return c.getConfig().getTrueFilter();
                                                    return c.getConfig().trueFilter();
                                                case 1:
                                                    return value.get(0);
                                                default:
@@ -395,8 +559,8 @@
                    @Override
                    public Void visitBooleanLiteralFilter(final ResultHandler<Filter> p,
                            final boolean value) {
                        p.handleResult(value ? c.getConfig().getTrueFilter() : c.getConfig()
                                .getFalseFilter());
                        p.handleResult(value ? c.getConfig().trueFilter() : c.getConfig()
                                .falseFilter());
                        return null;
                    }
@@ -465,11 +629,11 @@
                            public Filter apply(final Filter value, final Void p) {
                                if (value == null) {
                                    // Filter component did not match any attribute mappers.
                                    return c.getConfig().getTrueFilter();
                                } else if (value == c.getConfig().getFalseFilter()) {
                                    return c.getConfig().getTrueFilter();
                                } else if (value == c.getConfig().getTrueFilter()) {
                                    return c.getConfig().getFalseFilter();
                                    return c.getConfig().trueFilter();
                                } else if (value == c.getConfig().falseFilter()) {
                                    return c.getConfig().trueFilter();
                                } else if (value == c.getConfig().trueFilter()) {
                                    return c.getConfig().falseFilter();
                                } else {
                                    return Filter.not(value);
                                }
@@ -494,15 +658,15 @@
                                                    if (f == null) {
                                                        // Filter component did not match any attribute mappers.
                                                        i.remove();
                                                    } else if (f == c.getConfig().getFalseFilter()) {
                                                    } else if (f == c.getConfig().falseFilter()) {
                                                        i.remove();
                                                    } else if (f == c.getConfig().getTrueFilter()) {
                                                        return c.getConfig().getTrueFilter();
                                                    } else if (f == c.getConfig().trueFilter()) {
                                                        return c.getConfig().trueFilter();
                                                    }
                                                }
                                                switch (value.size()) {
                                                case 0:
                                                    return c.getConfig().getFalseFilter();
                                                    return c.getConfig().falseFilter();
                                                case 1:
                                                    return value.get(0);
                                                default:
@@ -536,7 +700,16 @@
        queryFilter.accept(visitor, h);
    }
    private Context wrap(ServerContext context) {
    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/Utils.java
@@ -173,7 +173,7 @@
            break;
        case EXTENDED:
        default:
            filter = c.getConfig().getFalseFilter(); // Not supported.
            filter = c.getConfig().falseFilter(); // Not supported.
            break;
        }
        return filter;
opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
@@ -50,50 +50,52 @@
     */
    public static void main(final String[] args) throws Exception {
        // All LDAP resources will use this connection factory.
        final ConnectionFactory ldapFactory = newAuthenticatedConnectionFactory(
                new LDAPConnectionFactory("localhost", 1389), Requests.newSimpleBindRequest(
                        "cn=directory manager", "password".toCharArray()));
        // Create two entry containers whose members reference each other.
        final EntryContainer userContainer = new EntryContainer(DN
                .valueOf("ou=people,dc=example,dc=com"), ldapFactory);
        final EntryContainer groupContainer = new EntryContainer(DN
                .valueOf("ou=groups,dc=example,dc=com"), ldapFactory);
        final ConnectionFactory ldapFactory =
                newAuthenticatedConnectionFactory(new LDAPConnectionFactory("localhost", 1389),
                        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(
                userContainer, userMapper);
        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(
                groupContainer, groupMapper);
        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);
        final org.forgerock.json.resource.ConnectionFactory resourceFactory = newInternalConnectionFactory(router);
        final org.forgerock.json.resource.ConnectionFactory resourceFactory =
                newInternalConnectionFactory(router);
        final HttpServer httpServer = HttpServer.createSimpleServer("./", PORT);
        try {
            final WebappContext ctx = new WebappContext("example", "/example");
            final ServletRegistration reg = ctx.addServlet("managed", new HttpServlet(
                    resourceFactory));
            final ServletRegistration reg =
                    ctx.addServlet("managed", new HttpServlet(resourceFactory));
            reg.addMapping("/managed/*");
            ctx.deploy(httpServer);