From b0a81eda9a0b2717e90dee49af53adc71f7cc3dc Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Thu, 31 Jan 2013 18:01:23 +0000
Subject: [PATCH] Fix OPENDJ-690: Rest2LDAP - Implement basic search support

---
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ComplexAttributeMapper.java         |   63 ++-
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java                        |   49 ++
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java         |   41 +
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java       |  150 +++---
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/EntryContainer.java                 |   52 +-
 opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java                        |    9 
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/FilterType.java                     |   87 ++++
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java                         |   45 ++
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ConstantAttributeMapper.java        |   97 ++++
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubContainerAttributeMapper.java    |   21 
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java          |   41 +
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java                          |  149 ++++++-
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java                |   50 ++
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java |  319 +++++++++++++--
 opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java       |   22 
 15 files changed, 945 insertions(+), 250 deletions(-)

diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java
index b9dd1c5..9240bac 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/AttributeMapper.java
@@ -11,7 +11,7 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
- * Copyright 2012 ForgeRock AS.
+ * Copyright 2012-2013 ForgeRock AS.
  */
 package org.forgerock.opendj.rest2ldap;
 
@@ -22,9 +22,9 @@
 import org.forgerock.json.fluent.JsonPointer;
 import org.forgerock.json.fluent.JsonValue;
 import org.forgerock.json.resource.ResultHandler;
-import org.forgerock.json.resource.ServerContext;
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.Entry;
+import org.forgerock.opendj.ldap.Filter;
 
 /**
  * An attribute mapper is responsible for converting JSON values to and from
@@ -40,13 +40,48 @@
      * Implementations should only add the names of attributes found in the LDAP
      * entry directly associated with the resource.
      *
+     * @param c
+     *            The context.
      * @param jsonAttribute
      *            The name of the resource attribute requested by the client.
      * @param ldapAttributes
      *            The set into which the required LDAP attribute names should be
      *            put.
      */
-    void getLDAPAttributes(JsonPointer jsonAttribute, Set<String> ldapAttributes);
+    void getLDAPAttributes(Context c, JsonPointer jsonAttribute, Set<String> ldapAttributes);
+
+    /**
+     * Transforms the provided REST comparison filter parameters to an LDAP
+     * filter representation, invoking a completion handler once the
+     * transformation has completed.
+     * <p>
+     * If this attribute mapper is not responsible for mapping the provided JSON
+     * attribute then the result handler's {@link ResultHandler#handleResult
+     * handleResult} method must be invoked with the value {@code null}. If this
+     * attribute mapper is responsible for mapping the JSON attribute, but an
+     * error occurred while constructing the LDAP filter, then the result
+     * handler's {@link ResultHandler#handleError handleError} method must be
+     * invoked with an appropriate exception indicating the problem which
+     * occurred.
+     *
+     * @param c
+     *            The context.
+     * @param type
+     *            The type of REST comparison filter.
+     * @param jsonAttribute
+     *            The name of the resource attribute to be filtered.
+     * @param operator
+     *            The name of the extended operator to use for the comparison,
+     *            or {@code null} if {@code type} is not
+     *            {@link FilterType#EXTENDED}.
+     * @param valueAssertion
+     *            The value assertion, or {@code null} if {@code type} is
+     *            {@link FilterType#PRESENT}.
+     * @param h
+     *            The result handler.
+     */
+    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
@@ -58,13 +93,13 @@
      * requests.
      *
      * @param c
-     *            The server context.
+     *            The context.
      * @param e
      *            The LDAP entry to be converted to JSON.
      * @param h
      *            The result handler.
      */
-    void toJSON(ServerContext c, Entry e, ResultHandler<Map<String, Object>> h);
+    void toJSON(Context c, Entry e, ResultHandler<Map<String, Object>> h);
 
     /**
      * Transforms JSON content in the provided JSON value to LDAP attributes,
@@ -75,16 +110,15 @@
      * requests.
      *
      * @param c
-     *            The server context.
+     *            The context.
      * @param v
      *            The JSON value to be converted to LDAP attributes.
      * @param h
      *            The result handler.
      */
-    void toLDAP(ServerContext c, JsonValue v, ResultHandler<List<Attribute>> h);
+    void toLDAP(Context c, JsonValue v, ResultHandler<List<Attribute>> h);
 
     // TODO: methods for obtaining schema information (e.g. name, description,
     // type information).
-    // TODO: methods for creating filters createLDAPEqualityFilter().
     // TODO: methods for creating sort controls.
 }
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ComplexAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ComplexAttributeMapper.java
index 84f0fa6..a5e5705 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ComplexAttributeMapper.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ComplexAttributeMapper.java
@@ -11,7 +11,7 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
- * Copyright 2012 ForgeRock AS.
+ * Copyright 2012-2013 ForgeRock AS.
  */
 package org.forgerock.opendj.rest2ldap;
 
@@ -26,9 +26,9 @@
 import org.forgerock.json.fluent.JsonValue;
 import org.forgerock.json.resource.ResourceException;
 import org.forgerock.json.resource.ResultHandler;
-import org.forgerock.json.resource.ServerContext;
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.Entry;
+import org.forgerock.opendj.ldap.Filter;
 
 /**
  * An attribute mapper which maps a single JSON attribute to the result of
@@ -60,10 +60,11 @@
      * {@inheritDoc}
      */
     @Override
-    public void getLDAPAttributes(final JsonPointer jsonAttribute, final Set<String> ldapAttributes) {
-        if (attributeMatchesPointer(jsonAttribute)) {
+    public void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
+            final Set<String> ldapAttributes) {
+        if (jsonAttribute.isEmpty() || matches(jsonAttribute)) {
             final JsonPointer relativePointer = jsonAttribute.relativePointer();
-            mapper.getLDAPAttributes(relativePointer, ldapAttributes);
+            mapper.getLDAPAttributes(c, relativePointer, ldapAttributes);
         }
     }
 
@@ -71,22 +72,39 @@
      * {@inheritDoc}
      */
     @Override
-    public void toJSON(final ServerContext c, final Entry e,
+    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)) {
+            final JsonPointer relativePointer = jsonAttribute.relativePointer();
+            mapper.getLDAPFilter(c, type, relativePointer, operator, valueAssertion, h);
+        } else {
+            // This attribute mapper cannot handle the provided filter component.
+            h.handleResult(null);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public 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>>() {
+        final ResultHandler<Map<String, Object>> wrapper =
+                new ResultHandler<Map<String, Object>>() {
 
-            @Override
-            public void handleError(final ResourceException e) {
-                h.handleError(e);
-            }
+                    @Override
+                    public void handleError(final ResourceException e) {
+                        h.handleError(e);
+                    }
 
-            @Override
-            public void handleResult(final Map<String, Object> result) {
-                final Map<String, Object> complexResult = Collections.singletonMap(
-                        jsonAttributeName, (Object) result);
-                h.handleResult(complexResult);
-            }
-        };
+                    @Override
+                    public void handleResult(final Map<String, Object> result) {
+                        final Map<String, Object> complexResult =
+                                Collections.singletonMap(jsonAttributeName, (Object) result);
+                        h.handleResult(complexResult);
+                    }
+                };
         mapper.toJSON(c, e, wrapper);
     }
 
@@ -94,15 +112,14 @@
      * {@inheritDoc}
      */
     @Override
-    public void toLDAP(final ServerContext c, final JsonValue v,
+    public void toLDAP(final Context c, final JsonValue v,
             final ResultHandler<List<Attribute>> h) {
         // TODO Auto-generated method stub
-
     }
 
-    private boolean attributeMatchesPointer(final JsonPointer resourceAttribute) {
-        return resourceAttribute.isEmpty()
-                || toLowerCase(resourceAttribute.get(0)).equals(normalizedJsonAttributeName);
+    private boolean matches(final JsonPointer jsonAttribute) {
+        return !jsonAttribute.isEmpty()
+                && toLowerCase(jsonAttribute.get(0)).equals(normalizedJsonAttributeName);
     }
 
 }
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java
index b69fcf8..9d7f367 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/CompositeAttributeMapper.java
@@ -11,26 +11,29 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
- * Copyright 2012 ForgeRock AS.
+ * Copyright 2012-2013 ForgeRock AS.
  */
 package org.forgerock.opendj.rest2ldap;
 
+import static org.forgerock.opendj.rest2ldap.Utils.accumulate;
+import static org.forgerock.opendj.rest2ldap.Utils.transform;
+
 import java.util.ArrayList;
 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;
-import java.util.concurrent.atomic.AtomicInteger;
 
 import org.forgerock.json.fluent.JsonPointer;
 import org.forgerock.json.fluent.JsonValue;
-import org.forgerock.json.resource.ResourceException;
 import org.forgerock.json.resource.ResultHandler;
-import org.forgerock.json.resource.ServerContext;
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.Entry;
+import org.forgerock.opendj.ldap.Filter;
+import org.forgerock.opendj.ldap.Function;
 
 /**
  * An attribute mapper which combines the results of a set of subordinate
@@ -62,9 +65,10 @@
      * {@inheritDoc}
      */
     @Override
-    public void getLDAPAttributes(final JsonPointer jsonAttribute, final Set<String> ldapAttributes) {
+    public void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
+            final Set<String> ldapAttributes) {
         for (final AttributeMapper attribute : attributeMappers) {
-            attribute.getLDAPAttributes(jsonAttribute, ldapAttributes);
+            attribute.getLDAPAttributes(c, jsonAttribute, ldapAttributes);
         }
     }
 
@@ -72,49 +76,68 @@
      * {@inheritDoc}
      */
     @Override
-    public void toJSON(final ServerContext c, final Entry e,
-            final ResultHandler<Map<String, Object>> h) {
-        final ResultHandler<Map<String, Object>> resultAccumulater = new ResultHandler<Map<String, Object>>() {
-            private final AtomicInteger latch = new AtomicInteger(attributeMappers.size());
-            private final List<Map<String, Object>> results = new ArrayList<Map<String, Object>>(
-                    latch.get());
+    public 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>() {
+                            @Override
+                            public Filter apply(final List<Filter> value, final Void p) {
+                                // Remove unmapped filters and combine using logical-OR.
+                                final Iterator<Filter> i = value.iterator();
+                                while (i.hasNext()) {
+                                    final Filter f = i.next();
+                                    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();
+                                    }
+                                }
+                                switch (value.size()) {
+                                case 0:
+                                    // No mappings found.
+                                    return null;
+                                case 1:
+                                    return value.get(0);
+                                default:
+                                    return Filter.or(value);
+                                }
+                            }
+                        }, h));
+        for (final AttributeMapper subMapper : attributeMappers) {
+            subMapper.getLDAPFilter(c, type, jsonAttribute, operator, valueAssertion, handler);
+        }
+    }
 
-            @Override
-            public void handleError(final ResourceException e) {
-                // Ensure that handler is only invoked once.
-                if (latch.getAndSet(0) > 0) {
-                    h.handleError(e);
-                }
-            }
-
-            @Override
-            public void handleResult(final Map<String, Object> result) {
-                if (result != null && !result.isEmpty()) {
-                    synchronized (this) {
-                        results.add(result);
-                    }
-                }
-                if (latch.decrementAndGet() == 0) {
-                    final Map<String, Object> mergeResult;
-                    switch (results.size()) {
-                    case 0:
-                        mergeResult = Collections.<String, Object> emptyMap();
-                        break;
-                    case 1:
-                        mergeResult = results.get(0);
-                        break;
-                    default:
-                        mergeResult = new LinkedHashMap<String, Object>();
-                        mergeJsonValues(results, mergeResult);
-                        break;
-                    }
-                    h.handleResult(mergeResult);
-                }
-            }
-        };
-
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public 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>() {
+                            @Override
+                            public Map<String, Object> apply(final List<Map<String, Object>> value,
+                                    final Void p) {
+                                switch (value.size()) {
+                                case 0:
+                                    return Collections.<String, Object> emptyMap();
+                                case 1:
+                                    return value.get(0) != null ? value.get(0) : Collections
+                                            .<String, Object> emptyMap();
+                                default:
+                                    return mergeJsonValues(value,
+                                            new LinkedHashMap<String, Object>());
+                                }
+                            }
+                        }, h));
         for (final AttributeMapper mapper : attributeMappers) {
-            mapper.toJSON(c, e, resultAccumulater);
+            mapper.toJSON(c, e, handler);
         }
     }
 
@@ -122,21 +145,10 @@
      * {@inheritDoc}
      */
     @Override
-    public void toLDAP(final ServerContext c, final JsonValue v,
-            final ResultHandler<List<Attribute>> h) {
+    public void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
         // TODO Auto-generated method stub
-
     }
 
-    /**
-     * Merge one JSON value into another.
-     *
-     * @param srcValue
-     *            The source value.
-     * @param dstValue
-     *            The destination value, into which which the value should be
-     *            merged.
-     */
     @SuppressWarnings("unchecked")
     private void mergeJsonValue(final Map<String, Object> srcValue,
             final Map<String, Object> dstValue) {
@@ -150,8 +162,8 @@
             } else if ((existingValue instanceof Map) && (newValue instanceof Map)) {
                 // Merge two maps - create a new Map, in case the existing one
                 // is unmodifiable.
-                existingValue = new LinkedHashMap<String, Object>(
-                        (Map<String, Object>) existingValue);
+                existingValue =
+                        new LinkedHashMap<String, Object>((Map<String, Object>) existingValue);
                 mergeJsonValue((Map<String, Object>) newValue, (Map<String, Object>) existingValue);
             } else if ((existingValue instanceof List) && (newValue instanceof List)) {
                 // Merge two lists- create a new List, in case the existing one
@@ -166,19 +178,13 @@
         }
     }
 
-    /**
-     * Merge the provided list of JSON values into a single value.
-     *
-     * @param srcValues
-     *            The source values.
-     * @param dstValue
-     *            The destination value, into which which the values should be
-     *            merged.
-     */
-    private void mergeJsonValues(final List<Map<String, Object>> srcValues,
+    private Map<String, Object> mergeJsonValues(final List<Map<String, Object>> srcValues,
             final Map<String, Object> dstValue) {
         for (final Map<String, Object> value : srcValues) {
-            mergeJsonValue(value, dstValue);
+            if (value != null) {
+                mergeJsonValue(value, dstValue);
+            }
         }
+        return dstValue;
     }
 }
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java
new file mode 100644
index 0000000..b36b195
--- /dev/null
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Config.java
@@ -0,0 +1,45 @@
+/*
+ * 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 org.forgerock.opendj.ldap.Filter;
+
+/**
+ * Common configuration options.
+ */
+public final class Config {
+
+    private final Filter trueFilter = Filter.objectClassPresent();
+    private final Filter falseFilter = Filter.present("1.1");
+
+    /**
+     * Returns the absolute true filter.
+     *
+     * @return The absolute true filter.
+     */
+    public Filter getTrueFilter() {
+        return trueFilter;
+    }
+
+    /**
+     * Returns the absolute false filter.
+     *
+     * @return The absolute false filter.
+     */
+    public Filter getFalseFilter() {
+        return falseFilter;
+    }
+}
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ConstantAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ConstantAttributeMapper.java
index 1468b9c..5fc4644 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ConstantAttributeMapper.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ConstantAttributeMapper.java
@@ -11,10 +11,12 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
- * Copyright 2012 ForgeRock AS.
+ * Copyright 2012-2013 ForgeRock AS.
  */
 package org.forgerock.opendj.rest2ldap;
 
+import static org.forgerock.opendj.rest2ldap.Utils.toLowerCase;
+
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -23,15 +25,14 @@
 import org.forgerock.json.fluent.JsonPointer;
 import org.forgerock.json.fluent.JsonValue;
 import org.forgerock.json.resource.ResultHandler;
-import org.forgerock.json.resource.ServerContext;
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.Entry;
+import org.forgerock.opendj.ldap.Filter;
 
 /**
  * An attribute mapper which maps a single JSON attribute to a fixed value.
  */
 public class ConstantAttributeMapper implements AttributeMapper {
-
     private final String jsonAttributeName;
     private final Object jsonAttributeValue;
 
@@ -53,7 +54,8 @@
      * {@inheritDoc}
      */
     @Override
-    public void getLDAPAttributes(final JsonPointer jsonAttribute, final Set<String> ldapAttributes) {
+    public void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
+            final Set<String> ldapAttributes) {
         // Nothing to do.
     }
 
@@ -61,8 +63,55 @@
      * {@inheritDoc}
      */
     @Override
-    public void toJSON(final ServerContext c, final Entry e,
-            final ResultHandler<Map<String, Object>> h) {
+    public 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) {
+                filter = c.getConfig().getTrueFilter();
+            } 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();
+                    break;
+                case STARTS_WITH:
+                    filter =
+                            v1.startsWith(v2) ? c.getConfig().getTrueFilter() : c.getConfig()
+                                    .getFalseFilter();
+                    break;
+                default:
+                    filter = compare(c, type, v1, v2);
+                    break;
+                }
+            } else if (jsonAttributeValue instanceof Number && valueAssertion instanceof Number) {
+                final Double v1 = ((Number) jsonAttributeValue).doubleValue();
+                final Double v2 = ((Number) valueAssertion).doubleValue();
+                filter = compare(c, type, v1, v2);
+            } else if (jsonAttributeValue instanceof Boolean && valueAssertion instanceof Boolean) {
+                final Boolean v1 = (Boolean) jsonAttributeValue;
+                final Boolean v2 = (Boolean) valueAssertion;
+                filter = compare(c, type, v1, v2);
+            } else {
+                // This attribute mapper is a candidate but it does not match.
+                filter = c.getConfig().getFalseFilter();
+            }
+            h.handleResult(filter);
+        } else {
+            // This attribute mapper cannot handle the provided filter component.
+            h.handleResult(null);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public 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));
 
@@ -72,10 +121,42 @@
      * {@inheritDoc}
      */
     @Override
-    public void toLDAP(final ServerContext c, final JsonValue v,
-            final ResultHandler<List<Attribute>> h) {
+    public 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) {
+        final Filter filter;
+        switch (type) {
+        case EQUAL_TO:
+            filter = v1.equals(v2) ? c.getConfig().getTrueFilter() : c.getConfig().getFalseFilter();
+            break;
+        case GREATER_THAN:
+            filter =
+                    v1.compareTo(v2) > 0 ? c.getConfig().getTrueFilter() : c.getConfig()
+                            .getFalseFilter();
+            break;
+        case GREATER_THAN_OR_EQUAL_TO:
+            filter =
+                    v1.compareTo(v2) >= 0 ? c.getConfig().getTrueFilter() : c.getConfig()
+                            .getFalseFilter();
+            break;
+        case LESS_THAN:
+            filter =
+                    v1.compareTo(v2) < 0 ? c.getConfig().getTrueFilter() : c.getConfig()
+                            .getFalseFilter();
+            break;
+        case LESS_THAN_OR_EQUAL_TO:
+            filter =
+                    v1.compareTo(v2) <= 0 ? c.getConfig().getTrueFilter() : c.getConfig()
+                            .getFalseFilter();
+            break;
+        default:
+            filter = c.getConfig().getFalseFilter(); // Not supported.
+            break;
+        }
+        return filter;
     }
 
 }
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java
new file mode 100644
index 0000000..5faf14f
--- /dev/null
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Context.java
@@ -0,0 +1,49 @@
+/*
+ * 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 org.forgerock.json.resource.ServerContext;
+
+/**
+ * Common context information passed to containers and mappers.
+ */
+public final class Context {
+    private final Config config;
+    private final ServerContext context;
+
+    Context(Config config, ServerContext context) {
+        this.config = config;
+        this.context = context;
+    }
+
+    /**
+     * Returns the common configuration options.
+     *
+     * @return The common configuration options.
+     */
+    public Config getConfig() {
+        return config;
+    }
+
+    /**
+     * Returns the commons REST server context passed in with the REST request.
+     *
+     * @return The commons REST server context passed in with the REST request.
+     */
+    public ServerContext getServerContext() {
+        return context;
+    }
+}
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java
index a0d8abb..8314c1b 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DefaultAttributeMapper.java
@@ -11,12 +11,13 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
- * Copyright 2012 ForgeRock AS.
+ * Copyright 2012-2013 ForgeRock AS.
  */
 package org.forgerock.opendj.rest2ldap;
 
 import static org.forgerock.opendj.rest2ldap.Utils.attributeToJson;
 import static org.forgerock.opendj.rest2ldap.Utils.getAttributeName;
+import static org.forgerock.opendj.rest2ldap.Utils.toFilter;
 import static org.forgerock.opendj.rest2ldap.Utils.toLowerCase;
 
 import java.util.LinkedHashMap;
@@ -27,18 +28,17 @@
 import org.forgerock.json.fluent.JsonPointer;
 import org.forgerock.json.fluent.JsonValue;
 import org.forgerock.json.resource.ResultHandler;
-import org.forgerock.json.resource.ServerContext;
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.Entry;
+import org.forgerock.opendj.ldap.Filter;
 
 /**
  * An attribute mapper that directly maps a configurable selection of attributes
  * to and from LDAP without any transformation.
  */
 public final class DefaultAttributeMapper implements AttributeMapper {
-
-    private final Map<String, String> excludedAttributes = new LinkedHashMap<String, String>();
     // 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>();
 
     /**
@@ -63,8 +63,12 @@
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public void getLDAPAttributes(final JsonPointer jsonAttribute, final Set<String> ldapAttributes) {
+    public void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
+            final Set<String> ldapAttributes) {
         switch (jsonAttribute.size()) {
         case 0:
             // Requested everything.
@@ -85,6 +89,21 @@
     }
 
     /**
+     * {@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 (jsonAttribute.size() == 1 && isIncludedAttribute(jsonAttribute.get(0))) {
+            h.handleResult(toFilter(c, type, jsonAttribute.get(0), valueAssertion));
+        } else {
+            // This attribute mapper cannot handle the provided filter component.
+            h.handleResult(null);
+        }
+    }
+
+    /**
      * Includes one or more LDAP attributes in this mapping.
      *
      * @param attributes
@@ -98,9 +117,11 @@
         return this;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public void toJSON(final ServerContext c, final Entry e,
-            final ResultHandler<Map<String, Object>> h) {
+    public void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
         final Map<String, Object> result = new LinkedHashMap<String, Object>(e.getAttributeCount());
         for (final Attribute a : e.getAllAttributes()) {
             final String name = getAttributeName(a);
@@ -111,9 +132,11 @@
         h.handleResult(result);
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
-    public void toLDAP(final ServerContext c, final JsonValue v,
-            final ResultHandler<List<Attribute>> h) {
+    public void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
         // TODO:
     }
 
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/EntryContainer.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/EntryContainer.java
index e318c3a..f6916de 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/EntryContainer.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/EntryContainer.java
@@ -11,13 +11,12 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
- * Copyright 2012 ForgeRock AS.
+ * Copyright 2012-2013 ForgeRock AS.
  */
 package org.forgerock.opendj.rest2ldap;
 
 import java.util.Collection;
 
-import org.forgerock.json.resource.Context;
 import org.forgerock.opendj.ldap.Connection;
 import org.forgerock.opendj.ldap.ConnectionFactory;
 import org.forgerock.opendj.ldap.DN;
@@ -119,6 +118,7 @@
     // FIXME: make this configurable, also allow use of DN.
     private static final String UUID_ATTRIBUTE = "entryUUID";
 
+    // TODO: support template variables.
     private final DN baseDN;
 
     private final ConnectionFactory factory;
@@ -164,27 +164,30 @@
      *
      * @param context
      *            The request context.
+     * @param filter
+     *            The LDAP search filter.
      * @param attributes
      *            The list of LDAP attributes to be returned.
      * @param handler
      *            The search result handler.
      */
-    public void listEntries(final Context context, final Collection<String> attributes,
-            final SearchResultHandler handler) {
+    public void listEntries(final Context context, final Filter filter,
+            final Collection<String> attributes, final SearchResultHandler handler) {
         final String[] tmp = getSearchAttributes(attributes);
-        final ConnectionCompletionHandler<Result> outerHandler = new ConnectionCompletionHandler<Result>(
-                handler) {
+        final ConnectionCompletionHandler<Result> outerHandler =
+                new ConnectionCompletionHandler<Result>(handler) {
 
-            @Override
-            public void handleResult(final Connection connection) {
-                final SearchRequestCompletionHandler innerHandler = new SearchRequestCompletionHandler(
-                        connection, handler);
-                final SearchRequest request = Requests.newSearchRequest(baseDN,
-                        SearchScope.SINGLE_LEVEL, Filter.objectClassPresent(), tmp);
-                connection.searchAsync(request, null, innerHandler);
-            }
+                    @Override
+                    public void handleResult(final Connection connection) {
+                        final SearchRequestCompletionHandler innerHandler =
+                                new SearchRequestCompletionHandler(connection, handler);
+                        final SearchRequest request =
+                                Requests.newSearchRequest(baseDN, SearchScope.SINGLE_LEVEL, filter,
+                                        tmp);
+                        connection.searchAsync(request, null, innerHandler);
+                    }
 
-        };
+                };
 
         factory.getConnectionAsync(outerHandler);
     }
@@ -208,16 +211,17 @@
         final ConnectionCompletionHandler<SearchResultEntry> outerHandler =
                 new ConnectionCompletionHandler<SearchResultEntry>(handler) {
 
-            @Override
-            public void handleResult(final Connection connection) {
-                final RequestCompletionHandler<SearchResultEntry> innerHandler =
-                        new RequestCompletionHandler<SearchResultEntry>(connection, handler);
-                final SearchRequest request = Requests.newSearchRequest(baseDN,
-                        SearchScope.SINGLE_LEVEL, Filter.equality(UUID_ATTRIBUTE, id), tmp);
-                connection.searchSingleEntryAsync(request, innerHandler);
-            }
+                    @Override
+                    public void handleResult(final Connection connection) {
+                        final RequestCompletionHandler<SearchResultEntry> innerHandler =
+                                new RequestCompletionHandler<SearchResultEntry>(connection, handler);
+                        final SearchRequest request =
+                                Requests.newSearchRequest(baseDN, SearchScope.SINGLE_LEVEL, Filter
+                                        .equality(UUID_ATTRIBUTE, id), tmp);
+                        connection.searchSingleEntryAsync(request, innerHandler);
+                    }
 
-        };
+                };
         // @Checkstyle:on
         factory.getConnectionAsync(outerHandler);
     }
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/FilterType.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/FilterType.java
new file mode 100644
index 0000000..83fe8a2
--- /dev/null
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/FilterType.java
@@ -0,0 +1,87 @@
+/*
+ * 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 org.forgerock.json.resource.QueryFilter;
+
+/**
+ * An enumeration of the commons REST query comparison filter types.
+ */
+public enum FilterType {
+
+    /**
+     * Substring filter.
+     *
+     * @see QueryFilter#contains
+     */
+    CONTAINS,
+
+    /**
+     * Equality filter.
+     *
+     * @see QueryFilter#equalTo
+     */
+    EQUAL_TO,
+
+    /**
+     * Extended match filter.
+     *
+     * @see QueryFilter#comparisonFilter
+     */
+    EXTENDED,
+
+    /**
+     * Greater than ordering filter.
+     *
+     * @see QueryFilter#greaterThan
+     */
+    GREATER_THAN,
+
+    /**
+     * Greater than or equal to ordering filter.
+     *
+     * @see QueryFilter#greaterThanOrEqualTo
+     */
+    GREATER_THAN_OR_EQUAL_TO,
+
+    /**
+     * Less than ordering filter.
+     *
+     * @see QueryFilter#lessThan
+     */
+    LESS_THAN,
+
+    /**
+     * Less than or equal to ordering filter.
+     *
+     * @see QueryFilter#lessThanOrEqualTo
+     */
+    LESS_THAN_OR_EQUAL_TO,
+
+    /**
+     * Presence filter.
+     *
+     * @see QueryFilter#present
+     */
+    PRESENT,
+
+    /**
+     * Initial sub-string filter.
+     *
+     * @see QueryFilter#startsWith
+     */
+    STARTS_WITH;
+}
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
index 0ff5003..5ef4c76 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/LDAPCollectionResourceProvider.java
@@ -11,12 +11,17 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
- * Copyright 2012 ForgeRock AS.
+ * Copyright 2012-2013 ForgeRock AS.
  */
 package org.forgerock.opendj.rest2ldap;
 
+import static org.forgerock.opendj.rest2ldap.Utils.accumulate;
+import static org.forgerock.opendj.rest2ldap.Utils.transform;
+
 import java.util.Collection;
+import java.util.Iterator;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -31,6 +36,8 @@
 import org.forgerock.json.resource.DeleteRequest;
 import org.forgerock.json.resource.NotSupportedException;
 import org.forgerock.json.resource.PatchRequest;
+import org.forgerock.json.resource.QueryFilter;
+import org.forgerock.json.resource.QueryFilterVisitor;
 import org.forgerock.json.resource.QueryRequest;
 import org.forgerock.json.resource.QueryResult;
 import org.forgerock.json.resource.QueryResultHandler;
@@ -47,6 +54,8 @@
 import org.forgerock.opendj.ldap.ConnectionException;
 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.TimeoutResultException;
@@ -62,8 +71,8 @@
     // Dummy exception used for signalling search success.
     private static final ResourceException SUCCESS = new UncategorizedException(0, null, null);
     private final AttributeMapper attributeMapper;
-
     private final EntryContainer entryContainer;
+    private final Config config = new Config();
 
     /**
      * Creates a new LDAP resource.
@@ -131,13 +140,12 @@
     @Override
     public void queryCollection(final ServerContext context, final QueryRequest request,
             final QueryResultHandler handler) {
-        final Set<JsonPointer> requestedAttributes = new LinkedHashSet<JsonPointer>();
-        final Collection<String> requestedLDAPAttributes = getRequestedLDAPAttributes(requestedAttributes);
-
         // List the entries.
+        final Context c = wrap(context);
         final SearchResultHandler searchHandler = new SearchResultHandler() {
             private final AtomicInteger pendingResourceCount = new AtomicInteger();
-            private final AtomicReference<ResourceException> pendingResult = new AtomicReference<ResourceException>();
+            private final AtomicReference<ResourceException> pendingResult =
+                    new AtomicReference<ResourceException>();
             private final AtomicBoolean resultSent = new AtomicBoolean();
 
             @Override
@@ -155,25 +163,27 @@
                 // mapping?
                 final String id = entryContainer.getIDFromEntry(entry);
                 final String revision = entryContainer.getEtagFromEntry(entry);
-                final ResultHandler<Map<String, Object>> mapHandler = new ResultHandler<Map<String, Object>>() {
-                    @Override
-                    public void handleError(final ResourceException e) {
-                        pendingResult.compareAndSet(null, e);
-                        pendingResourceCount.decrementAndGet();
-                        completeIfNecessary();
-                    }
+                final ResultHandler<Map<String, Object>> mapHandler =
+                        new ResultHandler<Map<String, Object>>() {
+                            @Override
+                            public void handleError(final ResourceException e) {
+                                pendingResult.compareAndSet(null, e);
+                                pendingResourceCount.decrementAndGet();
+                                completeIfNecessary();
+                            }
 
-                    @Override
-                    public void handleResult(final Map<String, Object> result) {
-                        final Resource resource = new Resource(id, revision, new JsonValue(result));
-                        handler.handleResource(resource);
-                        pendingResourceCount.decrementAndGet();
-                        completeIfNecessary();
-                    }
-                };
+                            @Override
+                            public void handleResult(final Map<String, Object> result) {
+                                final Resource resource =
+                                        new Resource(id, revision, new JsonValue(result));
+                                handler.handleResource(resource);
+                                pendingResourceCount.decrementAndGet();
+                                completeIfNecessary();
+                            }
+                        };
 
                 pendingResourceCount.incrementAndGet();
-                attributeMapper.toJSON(context, entry, mapHandler);
+                attributeMapper.toJSON(c, entry, mapHandler);
                 return true;
             }
 
@@ -213,7 +223,24 @@
                 }
             }
         };
-        entryContainer.listEntries(context, requestedLDAPAttributes, searchHandler);
+
+        final Collection<String> ldapAttributes = getLDAPAttributes(c, request.getFieldFilters());
+        getLDAPFilter(c, request.getQueryFilter(), new ResultHandler<Filter>() {
+            @Override
+            public void handleError(final ResourceException error) {
+                handler.handleError(error);
+            }
+
+            @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()) {
+                    handler.handleResult(new QueryResult());
+                } else {
+                    entryContainer.listEntries(c, ldapFilter, ldapAttributes, searchHandler);
+                }
+            }
+        });
     }
 
     /**
@@ -222,39 +249,39 @@
     @Override
     public void readInstance(final ServerContext context, final String resourceId,
             final ReadRequest request, final ResultHandler<Resource> handler) {
-        // TODO: Determine the set of LDAP attributes that need to be read.
-        final Set<JsonPointer> requestedAttributes = new LinkedHashSet<JsonPointer>();
-        final Collection<String> requestedLDAPAttributes = getRequestedLDAPAttributes(requestedAttributes);
-
+        final Context c = wrap(context);
         // @Checkstyle:off
         final org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry> searchHandler =
                 new org.forgerock.opendj.ldap.ResultHandler<SearchResultEntry>() {
-            @Override
-            public void handleErrorResult(final ErrorResultException error) {
-                handler.handleError(adaptErrorResult(error));
-            }
-
-            @Override
-            public void handleResult(final SearchResultEntry entry) {
-                final String revision = entryContainer.getEtagFromEntry(entry);
-                final ResultHandler<Map<String, Object>> mapHandler = new ResultHandler<Map<String, Object>>() {
                     @Override
-                    public void handleError(final ResourceException e) {
-                        handler.handleError(e);
+                    public void handleErrorResult(final ErrorResultException error) {
+                        handler.handleError(adaptErrorResult(error));
                     }
 
                     @Override
-                    public void handleResult(final Map<String, Object> result) {
-                        final Resource resource = new Resource(resourceId, revision, new JsonValue(
-                                result));
-                        handler.handleResult(resource);
+                    public void handleResult(final SearchResultEntry entry) {
+                        final String revision = entryContainer.getEtagFromEntry(entry);
+                        final ResultHandler<Map<String, Object>> mapHandler =
+                                new ResultHandler<Map<String, Object>>() {
+                                    @Override
+                                    public void handleError(final ResourceException e) {
+                                        handler.handleError(e);
+                                    }
+
+                                    @Override
+                                    public void handleResult(final Map<String, Object> result) {
+                                        final Resource resource =
+                                                new Resource(resourceId, revision, new JsonValue(
+                                                        result));
+                                        handler.handleResult(resource);
+                                    }
+                                };
+                        attributeMapper.toJSON(c, entry, mapHandler);
                     }
                 };
-                attributeMapper.toJSON(context, entry, mapHandler);
-            }
-        };
         // @Checkstyle:on
-        entryContainer.readEntry(context, resourceId, requestedLDAPAttributes, searchHandler);
+        final Collection<String> ldapAttributes = getLDAPAttributes(c, request.getFieldFilters());
+        entryContainer.readEntry(c, resourceId, ldapAttributes, searchHandler);
     }
 
     /**
@@ -299,27 +326,217 @@
 
     /**
      * Determines the set of LDAP attributes to request in an LDAP read (search,
-     * post-read), based on the provided set of JSON pointers.
+     * post-read), based on the provided list of JSON pointers.
      *
      * @param requestedAttributes
-     *            The set of resource attributes to be read.
+     *            The list of resource attributes to be read.
      * @return The set of LDAP attributes associated with the resource
      *         attributes.
      */
-    private Collection<String> getRequestedLDAPAttributes(final Set<JsonPointer> requestedAttributes) {
+    private Collection<String> getLDAPAttributes(final Context c,
+            final Collection<JsonPointer> requestedAttributes) {
         final Set<String> requestedLDAPAttributes;
         if (requestedAttributes.isEmpty()) {
             // Full read.
             requestedLDAPAttributes = new LinkedHashSet<String>();
-            attributeMapper.getLDAPAttributes(new JsonPointer(), requestedLDAPAttributes);
+            attributeMapper.getLDAPAttributes(c, new JsonPointer(), requestedLDAPAttributes);
         } else {
             // Partial read.
             requestedLDAPAttributes = new LinkedHashSet<String>(requestedAttributes.size());
             for (final JsonPointer requestedAttribute : requestedAttributes) {
-                attributeMapper.getLDAPAttributes(requestedAttribute, requestedLDAPAttributes);
+                attributeMapper.getLDAPAttributes(c, requestedAttribute, requestedLDAPAttributes);
             }
         }
         return requestedLDAPAttributes;
     }
 
+    private void getLDAPFilter(final Context c, final QueryFilter queryFilter,
+            final ResultHandler<Filter> h) {
+        final QueryFilterVisitor<Void, ResultHandler<Filter>> visitor =
+                new QueryFilterVisitor<Void, ResultHandler<Filter>>() {
+                    @Override
+                    public Void visitAndFilter(final ResultHandler<Filter> p,
+                            final List<QueryFilter> subFilters) {
+                        final ResultHandler<Filter> handler =
+                                accumulate(subFilters.size(), transform(
+                                        new Function<List<Filter>, Filter, Void>() {
+                                            @Override
+                                            public Filter apply(final List<Filter> value,
+                                                    final Void p) {
+                                                // Check for unmapped filter components and optimize.
+                                                final Iterator<Filter> i = value.iterator();
+                                                while (i.hasNext()) {
+                                                    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()) {
+                                                        i.remove();
+                                                    }
+                                                }
+                                                switch (value.size()) {
+                                                case 0:
+                                                    return c.getConfig().getTrueFilter();
+                                                case 1:
+                                                    return value.get(0);
+                                                default:
+                                                    return Filter.and(value);
+                                                }
+                                            }
+                                        }, p));
+                        for (final QueryFilter subFilter : subFilters) {
+                            subFilter.accept(this, handler);
+                        }
+                        return null;
+                    }
+
+                    @Override
+                    public Void visitBooleanLiteralFilter(final ResultHandler<Filter> p,
+                            final boolean value) {
+                        p.handleResult(value ? c.getConfig().getTrueFilter() : c.getConfig()
+                                .getFalseFilter());
+                        return null;
+                    }
+
+                    @Override
+                    public Void visitContainsFilter(final ResultHandler<Filter> p,
+                            final JsonPointer field, final Object valueAssertion) {
+                        attributeMapper.getLDAPFilter(c, FilterType.CONTAINS, field, null,
+                                valueAssertion, p);
+                        return null;
+                    }
+
+                    @Override
+                    public Void visitEqualsFilter(final ResultHandler<Filter> p,
+                            final JsonPointer field, final Object valueAssertion) {
+                        attributeMapper.getLDAPFilter(c, FilterType.EQUAL_TO, field, null,
+                                valueAssertion, p);
+                        return null;
+                    }
+
+                    @Override
+                    public Void visitExtendedMatchFilter(final ResultHandler<Filter> p,
+                            final JsonPointer field, final String operator,
+                            final Object valueAssertion) {
+                        attributeMapper.getLDAPFilter(c, FilterType.EXTENDED, field, operator,
+                                valueAssertion, p);
+                        return null;
+                    }
+
+                    @Override
+                    public Void visitGreaterThanFilter(final ResultHandler<Filter> p,
+                            final JsonPointer field, final Object valueAssertion) {
+                        attributeMapper.getLDAPFilter(c, FilterType.GREATER_THAN, field, null,
+                                valueAssertion, p);
+                        return null;
+                    }
+
+                    @Override
+                    public Void visitGreaterThanOrEqualToFilter(final ResultHandler<Filter> p,
+                            final JsonPointer field, final Object valueAssertion) {
+                        attributeMapper.getLDAPFilter(c, FilterType.GREATER_THAN_OR_EQUAL_TO,
+                                field, null, valueAssertion, p);
+                        return null;
+                    }
+
+                    @Override
+                    public Void visitLessThanFilter(final ResultHandler<Filter> p,
+                            final JsonPointer field, final Object valueAssertion) {
+                        attributeMapper.getLDAPFilter(c, FilterType.LESS_THAN, field, null,
+                                valueAssertion, p);
+                        return null;
+                    }
+
+                    @Override
+                    public Void visitLessThanOrEqualToFilter(final ResultHandler<Filter> p,
+                            final JsonPointer field, final Object valueAssertion) {
+                        attributeMapper.getLDAPFilter(c, FilterType.LESS_THAN_OR_EQUAL_TO, field,
+                                null, valueAssertion, p);
+                        return null;
+                    }
+
+                    @Override
+                    public Void visitNotFilter(final ResultHandler<Filter> p,
+                            final QueryFilter subFilter) {
+                        subFilter.accept(this, transform(new Function<Filter, Filter, Void>() {
+                            @Override
+                            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();
+                                } else {
+                                    return Filter.not(value);
+                                }
+                            }
+                        }, p));
+                        return null;
+                    }
+
+                    @Override
+                    public Void visitOrFilter(final ResultHandler<Filter> p,
+                            final List<QueryFilter> subFilters) {
+                        final ResultHandler<Filter> handler =
+                                accumulate(subFilters.size(), transform(
+                                        new Function<List<Filter>, Filter, Void>() {
+                                            @Override
+                                            public Filter apply(final List<Filter> value,
+                                                    final Void p) {
+                                                // Check for unmapped filter components and optimize.
+                                                final Iterator<Filter> i = value.iterator();
+                                                while (i.hasNext()) {
+                                                    final Filter f = i.next();
+                                                    if (f == null) {
+                                                        // Filter component did not match any attribute mappers.
+                                                        i.remove();
+                                                    } else if (f == c.getConfig().getFalseFilter()) {
+                                                        i.remove();
+                                                    } else if (f == c.getConfig().getTrueFilter()) {
+                                                        return c.getConfig().getTrueFilter();
+                                                    }
+                                                }
+                                                switch (value.size()) {
+                                                case 0:
+                                                    return c.getConfig().getFalseFilter();
+                                                case 1:
+                                                    return value.get(0);
+                                                default:
+                                                    return Filter.or(value);
+                                                }
+                                            }
+                                        }, p));
+                        for (final QueryFilter subFilter : subFilters) {
+                            subFilter.accept(this, handler);
+                        }
+                        return null;
+                    }
+
+                    @Override
+                    public Void visitPresentFilter(final ResultHandler<Filter> p,
+                            final JsonPointer field) {
+                        attributeMapper.getLDAPFilter(c, FilterType.PRESENT, field, null, null, p);
+                        return null;
+                    }
+
+                    @Override
+                    public Void visitStartsWithFilter(final ResultHandler<Filter> p,
+                            final JsonPointer field, final Object valueAssertion) {
+                        attributeMapper.getLDAPFilter(c, FilterType.STARTS_WITH, field, null,
+                                valueAssertion, p);
+                        return null;
+                    }
+
+                };
+        // Note that the returned LDAP filter may be null if it could not be mapped by any attribute mappers.
+        queryFilter.accept(visitor, h);
+    }
+
+    private Context wrap(ServerContext context) {
+        return new Context(config, context);
+    }
 }
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
index 8ed53fc..645c14c 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/ReferenceAttributeMapper.java
@@ -11,7 +11,7 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
- * Copyright 2012 ForgeRock AS.
+ * Copyright 2012-2013 ForgeRock AS.
  */
 package org.forgerock.opendj.rest2ldap;
 
@@ -22,9 +22,9 @@
 import org.forgerock.json.fluent.JsonPointer;
 import org.forgerock.json.fluent.JsonValue;
 import org.forgerock.json.resource.ResultHandler;
-import org.forgerock.json.resource.ServerContext;
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.Entry;
+import org.forgerock.opendj.ldap.Filter;
 
 /**
  * An attribute mapper which maps DN valued LDAP attributes to resource IDs.
@@ -37,7 +37,16 @@
      * {@inheritDoc}
      */
     @Override
-    public void getLDAPAttributes(final JsonPointer jsonAttribute, final Set<String> ldapAttributes) {
+    public void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
+            final Set<String> ldapAttributes) {
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    public void getLDAPFilter(final Context c, final FilterType type,
+            final JsonPointer jsonAttribute, final String operator, final Object valueAssertion,
+            final ResultHandler<Filter> h) {
         // TODO Auto-generated method stub
 
     }
@@ -46,8 +55,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void toJSON(final ServerContext c, final Entry e,
-            final ResultHandler<Map<String, Object>> h) {
+    public void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
         // TODO Auto-generated method stub
 
     }
@@ -56,10 +64,8 @@
      * {@inheritDoc}
      */
     @Override
-    public void toLDAP(final ServerContext c, final JsonValue v,
-            final ResultHandler<List<Attribute>> h) {
+    public void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
         // TODO Auto-generated method stub
-
     }
 
 }
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
index 74ba121..8d6a1c5 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SimpleAttributeMapper.java
@@ -11,11 +11,12 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
- * Copyright 2012 ForgeRock AS.
+ * Copyright 2012-2013 ForgeRock AS.
  */
 package org.forgerock.opendj.rest2ldap;
 
 import static org.forgerock.opendj.rest2ldap.Utils.byteStringToJson;
+import static org.forgerock.opendj.rest2ldap.Utils.toFilter;
 import static org.forgerock.opendj.rest2ldap.Utils.toLowerCase;
 
 import java.util.Collections;
@@ -26,10 +27,10 @@
 import org.forgerock.json.fluent.JsonPointer;
 import org.forgerock.json.fluent.JsonValue;
 import org.forgerock.json.resource.ResultHandler;
-import org.forgerock.json.resource.ServerContext;
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.Entry;
+import org.forgerock.opendj.ldap.Filter;
 import org.forgerock.opendj.ldap.Function;
 import org.forgerock.opendj.ldap.Functions;
 
@@ -104,13 +105,29 @@
      * {@inheritDoc}
      */
     @Override
-    public void getLDAPAttributes(final JsonPointer jsonAttribute, final Set<String> ldapAttributes) {
-        if (attributeMatchesPointer(jsonAttribute)) {
+    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
@@ -141,12 +158,11 @@
      * {@inheritDoc}
      */
     @Override
-    public void toJSON(final ServerContext c, final Entry e,
-            final ResultHandler<Map<String, Object>> h) {
+    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 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);
@@ -163,15 +179,14 @@
      * {@inheritDoc}
      */
     @Override
-    public void toLDAP(final ServerContext c, final JsonValue v,
-            final ResultHandler<List<Attribute>> h) {
+    public void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
         // TODO Auto-generated method stub
 
     }
 
-    private boolean attributeMatchesPointer(final JsonPointer resourceAttribute) {
-        return resourceAttribute.isEmpty()
-                || toLowerCase(resourceAttribute.get(0)).equals(normalizedJsonAttributeName);
+    private boolean matches(final JsonPointer jsonAttribute) {
+        return !jsonAttribute.isEmpty()
+                && toLowerCase(jsonAttribute.get(0)).equals(normalizedJsonAttributeName);
     }
 
 }
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubContainerAttributeMapper.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubContainerAttributeMapper.java
index d5591aa..5b5de74 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubContainerAttributeMapper.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/SubContainerAttributeMapper.java
@@ -11,7 +11,7 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
- * Copyright 2012 ForgeRock AS.
+ * Copyright 2012-2013 ForgeRock AS.
  */
 package org.forgerock.opendj.rest2ldap;
 
@@ -22,9 +22,9 @@
 import org.forgerock.json.fluent.JsonPointer;
 import org.forgerock.json.fluent.JsonValue;
 import org.forgerock.json.resource.ResultHandler;
-import org.forgerock.json.resource.ServerContext;
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.Entry;
+import org.forgerock.opendj.ldap.Filter;
 
 /**
  * An attribute mapper which inlines LDAP attributes from subordinate LDAP
@@ -38,7 +38,16 @@
      * {@inheritDoc}
      */
     @Override
-    public void getLDAPAttributes(final JsonPointer jsonAttribute, final Set<String> ldapAttributes) {
+    public void getLDAPAttributes(final Context c, final JsonPointer jsonAttribute,
+            final Set<String> ldapAttributes) {
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
+    public void getLDAPFilter(final Context c, final FilterType type,
+            final JsonPointer jsonAttribute, final String operator, final Object valueAssertion,
+            final ResultHandler<Filter> h) {
         // TODO Auto-generated method stub
 
     }
@@ -47,8 +56,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void toJSON(final ServerContext c, final Entry e,
-            final ResultHandler<Map<String, Object>> h) {
+    public void toJSON(final Context c, final Entry e, final ResultHandler<Map<String, Object>> h) {
         // TODO Auto-generated method stub
 
     }
@@ -57,8 +65,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void toLDAP(final ServerContext c, final JsonValue v,
-            final ResultHandler<List<Attribute>> h) {
+    public void toLDAP(final Context c, final JsonValue v, final ResultHandler<List<Attribute>> h) {
         // TODO Auto-generated method stub
 
     }
diff --git a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
index 9d83322..9a66c9c 100644
--- a/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
+++ b/opendj3/opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
@@ -11,7 +11,7 @@
  * Header, with the fields enclosed by brackets [] replaced by your own identifying
  * information: "Portions Copyright [year] [name of copyright owner]".
  *
- * Copyright 2012 ForgeRock AS.
+ * Copyright 2012-2013 ForgeRock AS.
  */
 package org.forgerock.opendj.rest2ldap;
 
@@ -21,13 +21,18 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.xml.bind.DatatypeConverter;
 
+import org.forgerock.json.resource.ResourceException;
+import org.forgerock.json.resource.ResultHandler;
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.Filter;
 import org.forgerock.opendj.ldap.Function;
 import org.forgerock.opendj.ldap.Functions;
 import org.forgerock.opendj.ldap.schema.Syntax;
@@ -36,37 +41,85 @@
  * Internal utility methods.
  */
 final class Utils {
+    /**
+     * Implementation class for {@link #accumulate}.
+     *
+     * @param <V>
+     *            The type of result.
+     */
+    private static final class AccumulatingResultHandler<V> implements ResultHandler<V> {
+        private final ResultHandler<List<V>> handler;
+        private final AtomicInteger latch;
+        private final List<V> results;
+
+        private AccumulatingResultHandler(final int size, final ResultHandler<List<V>> handler) {
+            this.latch = new AtomicInteger(size);
+            this.results = new ArrayList<V>(size);
+            this.handler = handler;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void handleError(final ResourceException e) {
+            // Ensure that handler is only invoked once.
+            if (latch.getAndSet(0) > 0) {
+                handler.handleError(e);
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void handleResult(final V result) {
+            synchronized (results) {
+                results.add(result);
+            }
+            if (latch.decrementAndGet() == 0) {
+                handler.handleResult(results);
+            }
+        }
+
+    }
 
     // @Checkstyle:off
     private static final Function<ByteString, Object, Attribute> BYTESTRING_TO_JSON =
             new Function<ByteString, Object, Attribute>() {
-        @Override
-        public Object apply(final ByteString value, final Attribute a) {
-            final Syntax syntax = a.getAttributeDescription().getAttributeType().getSyntax();
-            if (syntax.equals(getBooleanSyntax())) {
-                return Functions.byteStringToBoolean().apply(value, null);
-            } else if (syntax.equals(getIntegerSyntax())) {
-                return Functions.byteStringToLong().apply(value, null);
-            } else if (syntax.equals(getGeneralizedTimeSyntax())) {
-                return DatatypeConverter.printDateTime(Functions.byteStringToGeneralizedTime()
-                        .apply(value, null).toCalendar());
-            } else if (syntax.isHumanReadable()) {
-                return Functions.byteStringToString().apply(value, null);
-            } else {
-                // Base 64 encoded binary.
-                return DatatypeConverter.printBase64Binary(value.toByteArray());
-            }
-        }
-    };
-    // @Checkstyle:on
+                @Override
+                public Object apply(final ByteString value, final Attribute a) {
+                    final Syntax syntax =
+                            a.getAttributeDescription().getAttributeType().getSyntax();
+                    if (syntax.equals(getBooleanSyntax())) {
+                        return Functions.byteStringToBoolean().apply(value, null);
+                    } else if (syntax.equals(getIntegerSyntax())) {
+                        return Functions.byteStringToLong().apply(value, null);
+                    } else if (syntax.equals(getGeneralizedTimeSyntax())) {
+                        return DatatypeConverter.printDateTime(Functions
+                                .byteStringToGeneralizedTime().apply(value, null).toCalendar());
+                    } else if (syntax.isHumanReadable()) {
+                        return Functions.byteStringToString().apply(value, null);
+                    } else {
+                        // Base 64 encoded binary.
+                        return DatatypeConverter.printBase64Binary(value.toByteArray());
+                    }
+                }
+            };
+
+    static <V> ResultHandler<V> accumulate(final int size, final ResultHandler<List<V>> handler) {
+        return new AccumulatingResultHandler<V>(size, handler);
+    }
 
     static Object attributeToJson(final Attribute a) {
         final Function<ByteString, Object, Void> f = Functions.fixedFunction(BYTESTRING_TO_JSON, a);
-        final boolean isSingleValued = a.getAttributeDescription().getAttributeType()
-                .isSingleValue();
+        final boolean isSingleValued =
+                a.getAttributeDescription().getAttributeType().isSingleValue();
         return isSingleValued ? a.parse().as(f) : asList(a.parse().asSetOf(f));
     }
 
+    // @Checkstyle:on
+
     static Function<ByteString, Object, Attribute> byteStringToJson() {
         return BYTESTRING_TO_JSON;
     }
@@ -89,10 +142,62 @@
         return a.getAttributeDescription().withoutOption("binary").toString();
     }
 
+    static Filter toFilter(final Context c, final FilterType type, final String ldapAttribute,
+            final Object valueAssertion) {
+        final String v = String.valueOf(valueAssertion);
+        final Filter filter;
+        switch (type) {
+        case CONTAINS:
+            filter = Filter.substrings(ldapAttribute, null, Collections.singleton(v), null);
+            break;
+        case STARTS_WITH:
+            filter = Filter.substrings(ldapAttribute, v, null, null);
+            break;
+        case EQUAL_TO:
+            filter = Filter.equality(ldapAttribute, v);
+            break;
+        case GREATER_THAN:
+            filter = Filter.greaterThan(ldapAttribute, v);
+            break;
+        case GREATER_THAN_OR_EQUAL_TO:
+            filter = Filter.greaterOrEqual(ldapAttribute, v);
+            break;
+        case LESS_THAN:
+            filter = Filter.lessThan(ldapAttribute, v);
+            break;
+        case LESS_THAN_OR_EQUAL_TO:
+            filter = Filter.lessOrEqual(ldapAttribute, v);
+            break;
+        case PRESENT:
+            filter = Filter.present(ldapAttribute);
+            break;
+        case EXTENDED:
+        default:
+            filter = c.getConfig().getFalseFilter(); // Not supported.
+            break;
+        }
+        return filter;
+    }
+
     static String toLowerCase(final String s) {
         return s != null ? s.toLowerCase(Locale.ENGLISH) : null;
     }
 
+    static <M, N> ResultHandler<M> transform(final Function<M, N, Void> f,
+            final ResultHandler<N> handler) {
+        return new ResultHandler<M>() {
+            @Override
+            public void handleError(final ResourceException error) {
+                handler.handleError(error);
+            }
+
+            @Override
+            public void handleResult(final M result) {
+                handler.handleResult(f.apply(result, null));
+            }
+        };
+    }
+
     private static <T> List<T> asList(final Collection<T> c) {
         if (c instanceof List) {
             return (List<T>) c;
diff --git a/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java b/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
index 385aa48..0ec2721 100644
--- a/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
+++ b/opendj3/opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/Example.java
@@ -9,9 +9,9 @@
  * 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 Copyrighted [year] [name of copyright owner]".
+ * information: "Portions Copyright [year] [name of copyright owner]".
  *
- * Copyright © 2012 ForgeRock AS. All rights reserved.
+ * Copyright 2012-2013 ForgeRock AS.
  */
 
 package org.forgerock.opendj.rest2ldap;
@@ -22,7 +22,6 @@
 import java.util.logging.Logger;
 
 import org.forgerock.json.resource.Router;
-import org.forgerock.json.resource.RoutingMode;
 import org.forgerock.json.resource.servlet.HttpServlet;
 import org.forgerock.opendj.ldap.ConnectionFactory;
 import org.forgerock.opendj.ldap.DN;
@@ -86,8 +85,8 @@
 
         // Create the router.
         final Router router = new Router();
-        router.addRoute(RoutingMode.EQUALS, "/users", userResource);
-        router.addRoute(RoutingMode.EQUALS, "/groups", groupResource);
+        router.addRoute("/users", userResource);
+        router.addRoute("/groups", groupResource);
 
         final org.forgerock.json.resource.ConnectionFactory resourceFactory = newInternalConnectionFactory(router);
         final HttpServer httpServer = HttpServer.createSimpleServer("./", PORT);

--
Gitblit v1.10.0