From 7d5626f01a5ff72a8b9bbd803f6a3f4a0926a5da Mon Sep 17 00:00:00 2001
From: Nicolas Capponi <nicolas.capponi@forgerock.com>
Date: Tue, 07 Oct 2014 10:23:41 +0000
Subject: [PATCH] OPENDJ-1590 Migrate collation matching rules

---
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractOrderingMatchingRuleImpl.java            |   52 ++
 opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationLessThanMatchingRuleTest.java           |  119 +++++
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractMatchingRuleImpl.java                    |   10 
 opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationGreaterThanMatchingRuleTest.java        |  119 +++++
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractEqualityMatchingRuleImpl.java            |    3 
 opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationSubstringMatchingRuleTest.java          |  187 ++++++++
 opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationGreaterThanOrEqualMatchingRuleTest.java |  119 +++++
 opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImplTest.java       |   29 +
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImpl.java           |   57 +-
 opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationEqualityMatchingRuleTest.java           |  120 +++++
 opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationLessThanOrEqualMatchingRuleTest.java    |  119 +++++
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/CollationMatchingRulesImpl.java                  |  365 ++++++++++++++++
 12 files changed, 1,256 insertions(+), 43 deletions(-)

diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractEqualityMatchingRuleImpl.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractEqualityMatchingRuleImpl.java
index 1e69e23..0ccc622 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractEqualityMatchingRuleImpl.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractEqualityMatchingRuleImpl.java
@@ -39,8 +39,7 @@
  */
 abstract class AbstractEqualityMatchingRuleImpl extends AbstractMatchingRuleImpl {
 
-    private final Collection<? extends Indexer> indexers =
-            Collections.singleton(new DefaultIndexer("equality"));
+    private final Collection<? extends Indexer> indexers = Collections.singleton(new DefaultIndexer("equality"));
 
     AbstractEqualityMatchingRuleImpl() {
         // Nothing to do.
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractMatchingRuleImpl.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractMatchingRuleImpl.java
index a34d99d..5a159f3 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractMatchingRuleImpl.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractMatchingRuleImpl.java
@@ -51,11 +51,15 @@
         private final ByteSequence normalizedAssertionValue;
 
         static DefaultAssertion equality(final ByteSequence normalizedAssertionValue) {
-            return new DefaultAssertion("equality", normalizedAssertionValue);
+            return named("equality", normalizedAssertionValue);
         }
 
         static DefaultAssertion approximate(final ByteSequence normalizedAssertionValue) {
-            return new DefaultAssertion("approximate", normalizedAssertionValue);
+            return named("approximate", normalizedAssertionValue);
+        }
+
+        static DefaultAssertion named(final String indexID, final ByteSequence normalizedAssertionValue) {
+            return new DefaultAssertion(indexID, normalizedAssertionValue);
         }
 
         private DefaultAssertion(final String indexID, final ByteSequence normalizedAssertionValue) {
@@ -92,7 +96,7 @@
         public String getIndexID() {
             return indexID;
         }
-    };
+    }
 
     private static final Assertion UNDEFINED_ASSERTION = new Assertion() {
         @Override
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractOrderingMatchingRuleImpl.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractOrderingMatchingRuleImpl.java
index c069988..0648348 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractOrderingMatchingRuleImpl.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractOrderingMatchingRuleImpl.java
@@ -47,11 +47,19 @@
  */
 abstract class AbstractOrderingMatchingRuleImpl extends AbstractMatchingRuleImpl {
 
-    private final Collection<? extends Indexer> indexers =
-            Collections.singleton(new DefaultIndexer("ordering"));
+    private final Collection<? extends Indexer> indexers;
 
+    private final String indexId;
+
+    /** Constructor for default matching rules. */
     AbstractOrderingMatchingRuleImpl() {
-        // Nothing to do.
+        this("ordering");
+    }
+
+    /** Constructor for non-default matching rules. */
+    AbstractOrderingMatchingRuleImpl(String indexId) {
+        this.indexId = indexId;
+        this.indexers = Collections.singleton(new DefaultIndexer(indexId));
     }
 
     /** {@inheritDoc} */
@@ -67,15 +75,14 @@
 
             @Override
             public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException {
-                return factory.createRangeMatchQuery("ordering", ByteString.empty(), normAssertion, false, false);
+                return factory.createRangeMatchQuery(indexId, ByteString.empty(), normAssertion, false, false);
             }
         };
     }
 
     /** {@inheritDoc} */
     @Override
-    public Assertion getGreaterOrEqualAssertion(final Schema schema, final ByteSequence value)
-            throws DecodeException {
+    public Assertion getGreaterOrEqualAssertion(final Schema schema, final ByteSequence value) throws DecodeException {
         final ByteString normAssertion = normalizeAttributeValue(schema, value);
         return new Assertion() {
             @Override
@@ -85,7 +92,36 @@
 
             @Override
             public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException {
-                return factory.createRangeMatchQuery("ordering", normAssertion, ByteString.empty(), true, false);
+                return factory.createRangeMatchQuery(indexId, normAssertion, ByteString.empty(), true, false);
+            }
+        };
+    }
+
+    /**
+     * Retrieves the normalized form of the provided assertion value, which is
+     * best suited for efficiently performing greater than matching
+     * operations on that value. The assertion value is guaranteed to be valid
+     * against this matching rule's assertion syntax.
+     *
+     * @param schema
+     *            The schema in which this matching rule is defined.
+     * @param assertionValue
+     *            The syntax checked assertion value to be normalized.
+     * @return The normalized version of the provided assertion value.
+     * @throws DecodeException
+     *             if an syntax error occurred while parsing the value.
+     */
+    public Assertion getGreaterThanAssertion(Schema schema, ByteSequence assertionValue) throws DecodeException {
+        final ByteString normAssertion = normalizeAttributeValue(schema, assertionValue);
+        return new Assertion() {
+            @Override
+            public ConditionResult matches(final ByteSequence attributeValue) {
+                return ConditionResult.valueOf(attributeValue.compareTo(normAssertion) > 0);
+            }
+
+            @Override
+            public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException {
+                return factory.createRangeMatchQuery(indexId, normAssertion, ByteString.empty(), false, false);
             }
         };
     }
@@ -103,7 +139,7 @@
 
             @Override
             public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException {
-                return factory.createRangeMatchQuery("ordering", ByteString.empty(), normAssertion, false, true);
+                return factory.createRangeMatchQuery(indexId, ByteString.empty(), normAssertion, false, true);
             }
         };
     }
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImpl.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImpl.java
index 5d84db3..0ffb344 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImpl.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImpl.java
@@ -34,7 +34,6 @@
 import java.util.List;
 import java.util.TreeSet;
 
-import org.forgerock.i18n.LocalizableMessage;
 import org.forgerock.opendj.ldap.Assertion;
 import org.forgerock.opendj.ldap.ByteSequence;
 import org.forgerock.opendj.ldap.ByteString;
@@ -66,7 +65,7 @@
      * <li>normFinal will contain "final"</li>
      * </ul>
      */
-    static final class DefaultSubstringAssertion implements Assertion {
+    final class DefaultSubstringAssertion implements Assertion {
         /** Normalized substring for the text before the first '*' character. */
         private final ByteString normInitial;
         /** Normalized substrings for all text chunks in between '*' characters. */
@@ -74,8 +73,9 @@
         /** Normalized substring for the text after the last '*' character. */
         private final ByteString normFinal;
 
-        private DefaultSubstringAssertion(final ByteString normInitial,
-                final ByteString[] normAnys, final ByteString normFinal) {
+
+        private DefaultSubstringAssertion(final ByteString normInitial, final ByteString[] normAnys,
+                final ByteString normFinal) {
             this.normInitial = normInitial;
             this.normAnys = normAnys;
             this.normFinal = normFinal;
@@ -149,7 +149,7 @@
             final Collection<T> subqueries = new LinkedList<T>();
             if (normInitial != null) {
                 // relies on the fact that equality indexes are also ordered
-                subqueries.add(rangeMatch(factory, "equality", normInitial));
+                subqueries.add(rangeMatch(factory, equalityIndexId, normInitial));
             }
             if (normAnys != null) {
                 for (ByteString normAny : normAnys) {
@@ -164,6 +164,11 @@
                 // (possible overlapping with the use of equality index at the start of this method)
                 substringMatch(factory, normInitial, subqueries);
             }
+
+            if (normInitial == null && (normAnys == null || normAnys.length == 0) && normFinal == null) {
+                // Can happen with a filter like "cn:en.6:=*", just return an empty record
+                return factory.createMatchAllQuery();
+            }
             return factory.createIntersectionQuery(subqueries);
         }
 
@@ -197,7 +202,7 @@
             // There are two cases, depending on whether the user-provided
             // substring is smaller than the configured index substring length or not.
             if (normSubstring.length() < substrLength) {
-                subqueries.add(rangeMatch(factory, "substring", normSubstring));
+                subqueries.add(rangeMatch(factory, substringIndexId, normSubstring));
             } else {
                 // Break the value up into fragments of length equal to the
                 // index substring length, and read those keys.
@@ -213,7 +218,7 @@
                 }
 
                 for (ByteSequence key : substringKeys) {
-                    subqueries.add(factory.createExactMatchQuery("substring", key));
+                    subqueries.add(factory.createExactMatchQuery(substringIndexId, key));
                 }
             }
         }
@@ -243,15 +248,27 @@
         /** {@inheritDoc} */
         @Override
         public String getIndexID() {
-            return "substring";
+            return substringIndexId;
         }
     }
 
-    private final Collection<? extends Indexer> indexers =
-              Collections.singleton(new SubstringIndexer());
+    private final Collection<? extends Indexer> indexers = Collections.singleton(new SubstringIndexer());
 
+    /** Identifier of the substring index. */
+    private final String substringIndexId;
+
+    /** Identifier of the equality index. */
+    private final String equalityIndexId;
+
+    /** Constructor for default matching rules. */
     AbstractSubstringMatchingRuleImpl() {
-        // Nothing to do.
+        this("substring", "equality");
+    }
+
+    /** Constructor for non-default matching rules. */
+    AbstractSubstringMatchingRuleImpl(String substringIndexId, String equalityIndexId) {
+        this.substringIndexId = substringIndexId;
+        this.equalityIndexId = equalityIndexId;
     }
 
     /** {@inheritDoc} */
@@ -276,7 +293,7 @@
 
         ByteString bytes = evaluateEscapes(reader, escapeChars, false);
         if (bytes.length() > 0) {
-            initialString = normalizeSubString(schema, bytes);
+            initialString = bytes;
         }
         if (reader.remaining() == 0) {
             throw DecodeException.error(WARN_ATTR_SYNTAX_SUBSTRING_NO_WILDCARDS.get(assertionValue));
@@ -292,10 +309,10 @@
                 if (anyStrings == null) {
                     anyStrings = new LinkedList<ByteSequence>();
                 }
-                anyStrings.add(normalizeSubString(schema, bytes));
+                anyStrings.add(bytes);
             } else {
                 if (bytes.length() > 0) {
-                    finalString = normalizeSubString(schema, bytes);
+                    finalString = bytes;
                 }
                 break;
             }
@@ -399,17 +416,13 @@
                     }
                 }
             }
-            final LocalizableMessage message = ERR_INVALID_ESCAPE_CHAR.get(reader.getString(), c1);
-            throw DecodeException.error(message);
+            throw DecodeException.error(ERR_INVALID_ESCAPE_CHAR.get(reader.getString(), c1));
         }
 
         // The two positions must be the hex characters that
         // comprise the escaped value.
         if (reader.remaining() == 0) {
-            final LocalizableMessage message =
-                    ERR_HEX_DECODE_INVALID_LENGTH.get(reader.getString());
-
-            throw DecodeException.error(message);
+            throw DecodeException.error(ERR_HEX_DECODE_INVALID_LENGTH.get(reader.getString()));
         }
 
         final char c2 = reader.read();
@@ -469,9 +482,7 @@
             b |= 0x0F;
             break;
         default:
-            final LocalizableMessage message =
-                    ERR_HEX_DECODE_INVALID_CHARACTER.get(new String(new char[] { c1, c2 }), c1);
-            throw DecodeException.error(message);
+            throw DecodeException.error(ERR_HEX_DECODE_INVALID_CHARACTER.get(new String(new char[] { c1, c2 }), c1));
         }
         return (char) b;
     }
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/CollationMatchingRulesImpl.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/CollationMatchingRulesImpl.java
new file mode 100644
index 0000000..f8afddb
--- /dev/null
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/CollationMatchingRulesImpl.java
@@ -0,0 +1,365 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2014 ForgeRock AS.
+ */
+package org.forgerock.opendj.ldap.schema;
+
+import java.text.Collator;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import org.forgerock.i18n.LocalizedIllegalArgumentException;
+import org.forgerock.opendj.ldap.Assertion;
+import org.forgerock.opendj.ldap.ByteSequence;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.DecodeException;
+import org.forgerock.opendj.ldap.spi.Indexer;
+
+/**
+ * Implementations of collation matching rules. Each matching rule is created
+ * from a locale (eg, "en-US" or "en-GB").
+ * <p>
+ * The PRIMARY strength is used for collation, which means that only primary
+ * differences are considered significant for comparison. It usually means that
+ * spaces, case, and accent are not significant, although it is language dependant.
+ * <p>
+ * For a given locale, two indexes are used: a shared one (for equality and
+ * ordering rules) and a substring one (for substring rule).
+ */
+public final class CollationMatchingRulesImpl {
+
+    private static final String INDEX_ID_SHARED = "shared";
+    private static final String INDEX_ID_SUBSTRING = "substring";
+
+    private CollationMatchingRulesImpl() {
+        // This class is not instanciable
+    }
+
+    /**
+     * Creates a collation equality matching Rule.
+     *
+     * @param locale
+     *            the locale to use for this rule
+     * @return the matching rule implementation
+     */
+    public static CollationEqualityMatchingRuleImpl equalityMatchingRule(Locale locale) {
+        return new CollationEqualityMatchingRuleImpl(locale);
+    }
+
+    /**
+     * Creates a collation substring matching Rule.
+     *
+     * @param locale
+     *            the locale to use for this rule
+     * @return the matching rule implementation
+     */
+    public static CollationSubstringMatchingRuleImpl substringMatchingRule(Locale locale) {
+        return new CollationSubstringMatchingRuleImpl(locale);
+    }
+
+    /**
+     * Creates a collation less than matching Rule.
+     *
+     * @param locale
+     *            the locale to use for this rule
+     * @return the matching rule implementation
+     */
+    public static CollationLessThanMatchingRuleImpl lessThanMatchingRule(Locale locale) {
+        return new CollationLessThanMatchingRuleImpl(locale);
+    }
+
+    /**
+     * Creates a collation less than or equal matching Rule.
+     *
+     * @param locale
+     *            the locale to use for this rule
+     * @return the matching rule implementation
+     */
+    public static CollationLessThanOrEqualToMatchingRuleImpl lessThanOrEqualMatchingRule(Locale locale) {
+        return new CollationLessThanOrEqualToMatchingRuleImpl(locale);
+    }
+
+    /**
+     * Creates a collation greater than matching Rule.
+     *
+     * @param locale
+     *            the locale to use for this rule
+     * @return the matching rule implementation
+     */
+    public static CollationGreaterThanMatchingRuleImpl greaterThanMatchingRule(Locale locale) {
+        return new CollationGreaterThanMatchingRuleImpl(locale);
+    }
+
+    /**
+     * Creates a collation greater than or equal matching Rule.
+     *
+     * @param locale
+     *            the locale to use for this rule
+     * @return the matching rule implementation
+     */
+    public static CollationGreaterThanOrEqualToMatchingRuleImpl greaterThanOrEqualToMatchingRule(Locale locale) {
+        return new CollationGreaterThanOrEqualToMatchingRuleImpl(locale);
+    }
+
+    /**
+     * Defines the base for collation matching rules.
+     */
+    static abstract class AbstractCollationMatchingRuleImpl extends AbstractMatchingRuleImpl {
+
+        private final Locale locale;
+        final Collator collator;
+        final Indexer indexer;
+
+        /**
+         * Creates the collation matching rule with the provided locale.
+         *
+         * @param locale
+         *            Locale associated with this rule.
+         */
+        public AbstractCollationMatchingRuleImpl(Locale locale) {
+            this.locale = locale;
+            this.collator = createCollator(locale);
+            this.indexer = new DefaultIndexer(getSharedIndexName());
+        }
+
+        private Collator createCollator(Locale locale) {
+            Collator collator = Collator.getInstance(locale);
+            collator.setStrength(Collator.PRIMARY);
+            collator.setDecomposition(Collator.FULL_DECOMPOSITION);
+            return collator;
+        }
+
+        /**
+         * Returns the prefix name of the index database for this matching rule. An
+         * index name for this rule will be based upon the Locale. This will
+         * ensure that multiple collation matching rules corresponding to the
+         * same Locale can share the same index database.
+         *
+         * @return The prefix name of the index for this matching rule.
+         */
+        String getPrefixIndexName() {
+            String language = locale.getLanguage();
+            String country = locale.getCountry();
+            String variant = locale.getVariant();
+
+            StringBuilder builder = new StringBuilder(language);
+            if (country != null && country.length() > 0) {
+                builder.append("_");
+                builder.append(country);
+            }
+            if (variant != null && variant.length() > 0) {
+                builder.append("_");
+                builder.append(variant);
+            }
+            return builder.toString();
+        }
+
+        String getSharedIndexName() {
+            return getPrefixIndexName() + "." + INDEX_ID_SHARED;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Collection<? extends Indexer> getIndexers() {
+            return Collections.singletonList(indexer);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public ByteString normalizeAttributeValue(final Schema schema, final ByteSequence value)
+                throws DecodeException {
+            try {
+                final byte[] byteArray = collator.getCollationKey(value.toString()).toByteArray();
+                // Last 4 bytes are 0s when collator strength is set to PRIMARY, so skip them
+                return ByteString.wrap(byteArray).subSequence(0, byteArray.length - 4);
+            } catch (final LocalizedIllegalArgumentException e) {
+                throw DecodeException.error(e.getMessageObject());
+            }
+        }
+    }
+
+    /**
+     * Defines the collation equality matching rule.
+     */
+    static final class CollationEqualityMatchingRuleImpl extends AbstractCollationMatchingRuleImpl {
+
+        /**
+         * Creates the matching rule with the provided locale.
+         *
+         * @param locale
+         *          Locale associated with this rule.
+         */
+        CollationEqualityMatchingRuleImpl(Locale locale) {
+            super(locale);
+        }
+
+        @Override
+        public Assertion getAssertion(final Schema schema, final ByteSequence assertionValue)
+                throws DecodeException {
+            return DefaultAssertion.named(getSharedIndexName(), normalizeAttributeValue(schema, assertionValue));
+        }
+
+    }
+
+    /**
+     * Defines the collation substring matching rule.
+     */
+    static final class CollationSubstringMatchingRuleImpl extends AbstractCollationMatchingRuleImpl {
+
+        private final AbstractSubstringMatchingRuleImpl substringMatchingRule;
+        private final Indexer subIndexer;
+
+        /**
+         * Creates the matching rule with the provided locale.
+         *
+         * @param locale
+         *          Locale associated with this rule.
+         */
+        CollationSubstringMatchingRuleImpl(Locale locale) {
+            super(locale);
+            substringMatchingRule = new AbstractSubstringMatchingRuleImpl(
+                getPrefixIndexName() + "." + INDEX_ID_SUBSTRING, getSharedIndexName()) {
+                @Override
+                public ByteString normalizeAttributeValue(Schema schema, ByteSequence value)
+                        throws DecodeException {
+                    return CollationSubstringMatchingRuleImpl.this.normalizeAttributeValue(schema, value);
+                }
+            };
+            // default substring matching rule has exactly one indexer
+            subIndexer = substringMatchingRule.getIndexers().iterator().next();
+        }
+
+        @Override
+        public Assertion getAssertion(final Schema schema, final ByteSequence assertionValue) throws DecodeException {
+            return substringMatchingRule.getAssertion(schema, assertionValue);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Assertion getSubstringAssertion(Schema schema, ByteSequence subInitial,
+                List<? extends ByteSequence> subAnyElements, ByteSequence subFinal) throws DecodeException {
+            return substringMatchingRule.getSubstringAssertion(schema, subInitial, subAnyElements, subFinal);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public final Collection<? extends Indexer> getIndexers() {
+            return Arrays.asList(subIndexer, indexer);
+        }
+    }
+
+    /**
+     * Defines the collation ordering matching rule.
+     */
+    static abstract class CollationOrderingMatchingRuleImpl extends AbstractCollationMatchingRuleImpl {
+
+        final AbstractOrderingMatchingRuleImpl orderingMatchingRule;
+
+        /**
+         * Creates the matching rule with the provided locale.
+         *
+         * @param locale
+         *          Locale associated with this rule.
+         */
+        CollationOrderingMatchingRuleImpl(Locale locale) {
+            super(locale);
+
+            orderingMatchingRule = new AbstractOrderingMatchingRuleImpl(getSharedIndexName()) {
+                @Override
+                public ByteString normalizeAttributeValue(Schema schema, ByteSequence value) throws DecodeException {
+                    return CollationOrderingMatchingRuleImpl.this.normalizeAttributeValue(schema, value);
+                }
+            };
+        }
+    }
+
+    /**
+     * Defines the collation less than matching rule.
+     */
+    static final class CollationLessThanMatchingRuleImpl extends CollationOrderingMatchingRuleImpl {
+
+        CollationLessThanMatchingRuleImpl(Locale locale) {
+            super(locale);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Assertion getAssertion(Schema schema, ByteSequence assertionValue) throws DecodeException {
+            return orderingMatchingRule.getAssertion(schema, assertionValue);
+        }
+    }
+
+    /**
+     * Defines the collation less than or equal matching rule.
+     */
+    static final class CollationLessThanOrEqualToMatchingRuleImpl extends CollationOrderingMatchingRuleImpl {
+
+        CollationLessThanOrEqualToMatchingRuleImpl(Locale locale) {
+            super(locale);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Assertion getAssertion(Schema schema, ByteSequence assertionValue) throws DecodeException {
+            return orderingMatchingRule.getLessOrEqualAssertion(schema, assertionValue);
+        }
+    }
+
+    /**
+     * Defines the collation greater than matching rule.
+     */
+    static final class CollationGreaterThanMatchingRuleImpl extends CollationOrderingMatchingRuleImpl {
+
+        CollationGreaterThanMatchingRuleImpl(Locale locale) {
+            super(locale);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Assertion getAssertion(Schema schema, ByteSequence assertionValue) throws DecodeException {
+            return orderingMatchingRule.getGreaterThanAssertion(schema, assertionValue);
+        }
+    }
+
+    /**
+     * Defines the collation greater than or equal matching rule.
+     */
+    static final class CollationGreaterThanOrEqualToMatchingRuleImpl extends CollationOrderingMatchingRuleImpl {
+
+        CollationGreaterThanOrEqualToMatchingRuleImpl(Locale locale) {
+            super(locale);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public Assertion getAssertion(Schema schema, ByteSequence assertionValue) throws DecodeException {
+            return orderingMatchingRule.getGreaterOrEqualAssertion(schema, assertionValue);
+        }
+    }
+}
diff --git a/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImplTest.java b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImplTest.java
index 492a3e8..47f5600 100644
--- a/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImplTest.java
+++ b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImplTest.java
@@ -62,17 +62,24 @@
 
     }
 
-    private static class FakeIndexQueryFactory implements IndexQueryFactory<String> {
+    static class FakeIndexQueryFactory implements IndexQueryFactory<String> {
 
         private final IndexingOptions options;
+        private final boolean normalizedValuesAreReadable;
 
         public FakeIndexQueryFactory(IndexingOptions options) {
+            this(options, true);
+        }
+
+        public FakeIndexQueryFactory(IndexingOptions options, boolean normalizedValuesAreReadable) {
             this.options = options;
+            this.normalizedValuesAreReadable = normalizedValuesAreReadable;
         }
 
         @Override
         public String createExactMatchQuery(String indexID, ByteSequence key) {
-            return "exactMatch(" + indexID + ", value=='" + key + "')";
+            String keyValue = normalizedValuesAreReadable ? key.toString() : key.toByteString().toHexString();
+            return "exactMatch(" + indexID + ", value=='" + keyValue + "')";
         }
 
         @Override
@@ -81,13 +88,17 @@
         }
 
         @Override
-        public String createRangeMatchQuery(String indexID, ByteSequence lower,
-                ByteSequence upper, boolean lowerIncluded, boolean upperIncluded) {
+        public String createRangeMatchQuery(String indexID, ByteSequence lower, ByteSequence upper,
+                boolean lowerIncluded, boolean upperIncluded) {
             final StringBuilder sb = new StringBuilder("rangeMatch");
             sb.append("(");
             sb.append(indexID);
             sb.append(", '");
-            sb.append(lower);
+            if (normalizedValuesAreReadable) {
+                sb.append(lower);
+            } else if (!lower.isEmpty()) {
+                sb.append(lower.toByteString().toHexString());
+            }
             sb.append("' <");
             if (lowerIncluded) {
                 sb.append("=");
@@ -97,7 +108,11 @@
                 sb.append("=");
             }
             sb.append(" '");
-            sb.append(upper);
+            if (normalizedValuesAreReadable) {
+                sb.append(upper);
+            } else if (!upper.isEmpty()) {
+                sb.append(upper.toByteString().toHexString());
+            }
             sb.append("')");
             return sb.toString();
         }
@@ -123,7 +138,7 @@
         return new FakeSubstringMatchingRuleImpl();
     }
 
-    private IndexingOptions newIndexingOptions() {
+    static IndexingOptions newIndexingOptions() {
         final IndexingOptions options = mock(IndexingOptions.class);
         when(options.substringKeySize()).thenReturn(3);
         return options;
diff --git a/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationEqualityMatchingRuleTest.java b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationEqualityMatchingRuleTest.java
new file mode 100644
index 0000000..37abf89
--- /dev/null
+++ b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationEqualityMatchingRuleTest.java
@@ -0,0 +1,120 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2014 ForgeRock AS.
+ */
+package org.forgerock.opendj.ldap.schema;
+
+import java.util.Locale;
+
+import org.forgerock.opendj.ldap.Assertion;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ConditionResult;
+import org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.FakeIndexQueryFactory;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.*;
+import static org.testng.Assert.*;
+
+@SuppressWarnings("javadoc")
+@Test
+public class CollationEqualityMatchingRuleTest extends MatchingRuleTest {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @DataProvider(name = "matchingRuleInvalidAttributeValues")
+    public Object[][] createMatchingRuleInvalidAttributeValues() {
+        return new Object[][] { };
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @DataProvider(name = "matchingrules")
+    public Object[][] createMatchingRuleTest() {
+        return new Object[][] {
+            // \u00E9 = LATIN SMALL LETTER E WITH ACUTE
+            // \u00C9 = LATIN CAPITAL LETTER E WITH ACUTE
+            { "passe", "passe", ConditionResult.TRUE },
+            { "passe ", "passe", ConditionResult.TRUE },
+            { "passE", "passe", ConditionResult.TRUE },
+            { "pass\u00E9", "passe", ConditionResult.TRUE },
+            { "pass\u00E9", "passE", ConditionResult.TRUE },
+            { "pass\u00E9", "pass\u00C9", ConditionResult.TRUE },
+            { "passe", "pass\u00E9", ConditionResult.TRUE },
+            { "passE", "pass\u00E9", ConditionResult.TRUE },
+            { "pass\u00C9", "pass\u00E9", ConditionResult.TRUE },
+            { "abc", "abd", ConditionResult.FALSE },
+            { "abc", "acc", ConditionResult.FALSE },
+            { "abc", "bbc", ConditionResult.FALSE },
+            { "abc", "abD", ConditionResult.FALSE },
+            { "def", "d\u00E9g", ConditionResult.FALSE },
+            { "def", "dEg", ConditionResult.FALSE },
+            { "dEf", "deg", ConditionResult.FALSE },
+            { "d\u00E9f", "dEg", ConditionResult.FALSE },
+            { "abd", "abc", ConditionResult.FALSE },
+            { "acc", "abc", ConditionResult.FALSE },
+            { "bbc", "abc", ConditionResult.FALSE },
+            { "abD", "abc", ConditionResult.FALSE },
+            { "d\u00E9g", "def", ConditionResult.FALSE },
+            { "dEg", "def", ConditionResult.FALSE },
+            { "deg", "dEf", ConditionResult.FALSE },
+            { "dEg", "d\u00E9f", ConditionResult.FALSE },
+        };
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected MatchingRule getRule() {
+        // Note that oid and names are not used by the test (ie, they could be any value, test should pass anyway)
+        // Only the implementation class and the provided locale are really tested here.
+        String oid = "1.3.6.1.4.1.42.2.27.9.4.76.1.3";
+        Schema schema = new SchemaBuilder(Schema.getCoreSchema()).
+            buildMatchingRule(oid).
+                syntaxOID(SchemaConstants.SYNTAX_DIRECTORY_STRING_OID).
+                names("fr.eq").
+                implementation(CollationMatchingRulesImpl.equalityMatchingRule(new Locale("fr"))).
+                addToSchema().
+            toSchema();
+        return schema.getMatchingRule(oid);
+    }
+
+    @Test
+    public void testCreateIndexQuery() throws Exception {
+        ByteString value = ByteString.valueOf("abc");
+        MatchingRule matchingRule = getRule();
+        Assertion assertion = matchingRule.getAssertion(value);
+
+        String indexQuery = assertion.createIndexQuery(new FakeIndexQueryFactory(newIndexingOptions(), false));
+
+        ByteString normalizedValue = matchingRule.normalizeAttributeValue(value);
+        assertEquals(indexQuery, "exactMatch(fr.shared, value=='" + normalizedValue.toHexString() + "')");
+    }
+
+}
diff --git a/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationGreaterThanMatchingRuleTest.java b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationGreaterThanMatchingRuleTest.java
new file mode 100644
index 0000000..f9abe1f
--- /dev/null
+++ b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationGreaterThanMatchingRuleTest.java
@@ -0,0 +1,119 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2014 ForgeRock AS.
+ */
+package org.forgerock.opendj.ldap.schema;
+
+import java.util.Locale;
+
+import org.forgerock.opendj.ldap.Assertion;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ConditionResult;
+import org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.FakeIndexQueryFactory;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.*;
+import static org.testng.Assert.*;
+
+@SuppressWarnings("javadoc")
+@Test
+public class CollationGreaterThanMatchingRuleTest extends MatchingRuleTest {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @DataProvider(name = "matchingRuleInvalidAttributeValues")
+    public Object[][] createMatchingRuleInvalidAttributeValues() {
+        return new Object[][] { };
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @DataProvider(name = "matchingrules")
+    public Object[][] createMatchingRuleTest() {
+        return new Object[][] {
+            // \u00E9 = LATIN SMALL LETTER E WITH ACUTE
+            // \u00C9 = LATIN CAPITAL LETTER E WITH ACUTE
+            { "abc", "abd", ConditionResult.FALSE },
+            { "abc", "acc", ConditionResult.FALSE },
+            { "abc", "bbc", ConditionResult.FALSE },
+            { "abc", "abD", ConditionResult.FALSE },
+            { "def", "d\u00E9g", ConditionResult.FALSE },
+            { "def", "dEg", ConditionResult.FALSE },
+            { "dEf", "deg", ConditionResult.FALSE },
+            { "d\u00E9f", "dEg", ConditionResult.FALSE },
+            { "passe", "passe", ConditionResult.FALSE },
+            { "passe ", "passe", ConditionResult.FALSE },
+            { "passE", "passe", ConditionResult.FALSE },
+            { "pass\u00E9", "passe", ConditionResult.FALSE },
+            { "pass\u00E9", "passE", ConditionResult.FALSE },
+            { "pass\u00E9", "pass\u00C9", ConditionResult.FALSE },
+            { "passe", "pass\u00E9", ConditionResult.FALSE },
+            { "passE", "pass\u00E9", ConditionResult.FALSE },
+            { "pass\u00C9", "pass\u00E9", ConditionResult.FALSE },
+            { "abd", "abc", ConditionResult.TRUE },
+            { "acc", "abc", ConditionResult.TRUE },
+            { "bbc", "abc", ConditionResult.TRUE },
+            { "abD", "abc", ConditionResult.TRUE },
+            { "d\u00E9g", "def", ConditionResult.TRUE },
+            { "dEg", "def", ConditionResult.TRUE },
+            { "deg", "dEf", ConditionResult.TRUE },
+            { "dEg", "d\u00E9f", ConditionResult.TRUE },
+        };
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected MatchingRule getRule() {
+        // Note that oid and names are not used by the test (ie, they could be any value, test should pass anyway)
+        // Only the implementation class and the provided locale are really tested here.
+        String oid = "1.3.6.1.4.1.42.2.27.9.4.76.1.5";
+        Schema schema = new SchemaBuilder(Schema.getCoreSchema()).
+            buildMatchingRule(oid).
+                syntaxOID(SchemaConstants.SYNTAX_DIRECTORY_STRING_OID).
+                names("fr.gt").
+                implementation(CollationMatchingRulesImpl.greaterThanMatchingRule(new Locale("fr"))).
+                addToSchema().
+            toSchema();
+        return schema.getMatchingRule(oid);
+    }
+
+    @Test
+    public void testCreateIndexQuery() throws Exception {
+        ByteString value = ByteString.valueOf("abc");
+        MatchingRule matchingRule = getRule();
+        Assertion assertion = matchingRule.getAssertion(value);
+
+        String indexQuery = assertion.createIndexQuery(new FakeIndexQueryFactory(newIndexingOptions(), false));
+
+        ByteString normalizedValue = matchingRule.normalizeAttributeValue(value);
+        assertEquals(indexQuery, "rangeMatch(fr.shared, '" + normalizedValue.toHexString() + "' < value < '')");
+    }
+}
diff --git a/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationGreaterThanOrEqualMatchingRuleTest.java b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationGreaterThanOrEqualMatchingRuleTest.java
new file mode 100644
index 0000000..c70d0ce
--- /dev/null
+++ b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationGreaterThanOrEqualMatchingRuleTest.java
@@ -0,0 +1,119 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2014 ForgeRock AS.
+ */
+package org.forgerock.opendj.ldap.schema;
+
+import java.util.Locale;
+
+import org.forgerock.opendj.ldap.Assertion;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ConditionResult;
+import org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.FakeIndexQueryFactory;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.*;
+import static org.testng.Assert.*;
+
+@SuppressWarnings("javadoc")
+@Test
+public class CollationGreaterThanOrEqualMatchingRuleTest extends MatchingRuleTest {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @DataProvider(name = "matchingRuleInvalidAttributeValues")
+    public Object[][] createMatchingRuleInvalidAttributeValues() {
+        return new Object[][] { };
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @DataProvider(name = "matchingrules")
+    public Object[][] createMatchingRuleTest() {
+        return new Object[][] {
+            // \u00E9 = LATIN SMALL LETTER E WITH ACUTE
+            // \u00C9 = LATIN CAPITAL LETTER E WITH ACUTE
+            { "abc", "abd", ConditionResult.FALSE },
+            { "abc", "acc", ConditionResult.FALSE },
+            { "abc", "bbc", ConditionResult.FALSE },
+            { "abc", "abD", ConditionResult.FALSE },
+            { "def", "d\u00E9g", ConditionResult.FALSE },
+            { "def", "dEg", ConditionResult.FALSE },
+            { "dEf", "deg", ConditionResult.FALSE },
+            { "d\u00E9f", "dEg", ConditionResult.FALSE },
+            { "passe", "passe", ConditionResult.TRUE },
+            { "passe ", "passe", ConditionResult.TRUE },
+            { "passE", "passe", ConditionResult.TRUE },
+            { "pass\u00E9", "passe", ConditionResult.TRUE },
+            { "pass\u00E9", "passE", ConditionResult.TRUE },
+            { "pass\u00E9", "pass\u00C9", ConditionResult.TRUE },
+            { "passe", "pass\u00E9", ConditionResult.TRUE },
+            { "passE", "pass\u00E9", ConditionResult.TRUE },
+            { "pass\u00C9", "pass\u00E9", ConditionResult.TRUE },
+            { "abd", "abc", ConditionResult.TRUE },
+            { "acc", "abc", ConditionResult.TRUE },
+            { "bbc", "abc", ConditionResult.TRUE },
+            { "abD", "abc", ConditionResult.TRUE },
+            { "d\u00E9g", "def", ConditionResult.TRUE },
+            { "dEg", "def", ConditionResult.TRUE },
+            { "deg", "dEf", ConditionResult.TRUE },
+            { "dEg", "d\u00E9f", ConditionResult.TRUE },
+        };
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected MatchingRule getRule() {
+        // Note that oid and names are not used by the test (ie, they could be any value, test should pass anyway)
+        // Only the implementation class and the provided locale are really tested here.
+        String oid = "1.3.6.1.4.1.42.2.27.9.4.76.1.4";
+        Schema schema = new SchemaBuilder(Schema.getCoreSchema()).
+            buildMatchingRule(oid).
+                syntaxOID(SchemaConstants.SYNTAX_DIRECTORY_STRING_OID).
+                names("fr.gt2").
+                implementation(CollationMatchingRulesImpl.greaterThanOrEqualToMatchingRule(new Locale("fr"))).
+                addToSchema().
+            toSchema();
+        return schema.getMatchingRule(oid);
+    }
+
+    @Test
+    public void testCreateIndexQuery() throws Exception {
+        ByteString value = ByteString.valueOf("abc");
+        MatchingRule matchingRule = getRule();
+        Assertion assertion = matchingRule.getAssertion(value);
+
+        String indexQuery = assertion.createIndexQuery(new FakeIndexQueryFactory(newIndexingOptions(), false));
+
+        ByteString normalizedValue = matchingRule.normalizeAttributeValue(value);
+        assertEquals(indexQuery, "rangeMatch(fr.shared, '" + normalizedValue.toHexString() + "' <= value < '')");
+    }
+}
diff --git a/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationLessThanMatchingRuleTest.java b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationLessThanMatchingRuleTest.java
new file mode 100644
index 0000000..1d9dc7d
--- /dev/null
+++ b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationLessThanMatchingRuleTest.java
@@ -0,0 +1,119 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2014 ForgeRock AS.
+ */
+package org.forgerock.opendj.ldap.schema;
+
+import java.util.Locale;
+
+import org.forgerock.opendj.ldap.Assertion;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ConditionResult;
+import org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.FakeIndexQueryFactory;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.*;
+import static org.testng.Assert.*;
+
+@SuppressWarnings("javadoc")
+@Test
+public class CollationLessThanMatchingRuleTest extends MatchingRuleTest {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @DataProvider(name = "matchingRuleInvalidAttributeValues")
+    public Object[][] createMatchingRuleInvalidAttributeValues() {
+        return new Object[][] { };
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @DataProvider(name = "matchingrules")
+    public Object[][] createMatchingRuleTest() {
+        return new Object[][] {
+            // \u00E9 = LATIN SMALL LETTER E WITH ACUTE
+            // \u00C9 = LATIN CAPITAL LETTER E WITH ACUTE
+            { "abc", "abd", ConditionResult.TRUE },
+            { "abc", "acc", ConditionResult.TRUE },
+            { "abc", "bbc", ConditionResult.TRUE },
+            { "abc", "abD", ConditionResult.TRUE },
+            { "def", "d\u00E9g", ConditionResult.TRUE },
+            { "def", "dEg", ConditionResult.TRUE },
+            { "dEf", "deg", ConditionResult.TRUE },
+            { "d\u00E9f", "dEg", ConditionResult.TRUE },
+            { "passe", "passe", ConditionResult.FALSE },
+            { "passe ", "passe", ConditionResult.FALSE },
+            { "passE", "passe", ConditionResult.FALSE },
+            { "pass\u00E9", "passe", ConditionResult.FALSE },
+            { "pass\u00E9", "passE", ConditionResult.FALSE },
+            { "pass\u00E9", "pass\u00C9", ConditionResult.FALSE },
+            { "passe", "pass\u00E9", ConditionResult.FALSE },
+            { "passE", "pass\u00E9", ConditionResult.FALSE },
+            { "pass\u00C9", "pass\u00E9", ConditionResult.FALSE },
+            { "abd", "abc", ConditionResult.FALSE },
+            { "acc", "abc", ConditionResult.FALSE },
+            { "bbc", "abc", ConditionResult.FALSE },
+            { "abD", "abc", ConditionResult.FALSE },
+            { "d\u00E9g", "def", ConditionResult.FALSE },
+            { "dEg", "def", ConditionResult.FALSE },
+            { "deg", "dEf", ConditionResult.FALSE },
+            { "dEg", "d\u00E9f", ConditionResult.FALSE },
+        };
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected MatchingRule getRule() {
+        // Note that oid and names are not used by the test (ie, they could be any value, test should pass anyway)
+        // Only the implementation class and the provided locale are really tested here.
+        String oid = "1.3.6.1.4.1.42.2.27.9.4.76.1.1";
+        Schema schema = new SchemaBuilder(Schema.getCoreSchema()).
+            buildMatchingRule(oid).
+                syntaxOID(SchemaConstants.SYNTAX_DIRECTORY_STRING_OID).
+                names("fr.lt").
+                implementation(CollationMatchingRulesImpl.lessThanMatchingRule(new Locale("fr"))).
+                addToSchema().
+            toSchema();
+        return schema.getMatchingRule(oid);
+    }
+
+    @Test
+    public void testCreateIndexQuery() throws Exception {
+        ByteString value = ByteString.valueOf("abc");
+        MatchingRule matchingRule = getRule();
+        Assertion assertion = matchingRule.getAssertion(value);
+
+        String indexQuery = assertion.createIndexQuery(new FakeIndexQueryFactory(newIndexingOptions(), false));
+
+        ByteString normalizedValue = matchingRule.normalizeAttributeValue(value);
+        assertEquals(indexQuery, "rangeMatch(fr.shared, '' < value < '" + normalizedValue.toHexString() + "')");
+    }
+}
diff --git a/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationLessThanOrEqualMatchingRuleTest.java b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationLessThanOrEqualMatchingRuleTest.java
new file mode 100644
index 0000000..9dde742
--- /dev/null
+++ b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationLessThanOrEqualMatchingRuleTest.java
@@ -0,0 +1,119 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2014 ForgeRock AS.
+ */
+package org.forgerock.opendj.ldap.schema;
+
+import java.util.Locale;
+
+import org.forgerock.opendj.ldap.Assertion;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ConditionResult;
+import org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.FakeIndexQueryFactory;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.*;
+import static org.testng.Assert.*;
+
+@SuppressWarnings("javadoc")
+@Test
+public class CollationLessThanOrEqualMatchingRuleTest extends MatchingRuleTest {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @DataProvider(name = "matchingRuleInvalidAttributeValues")
+    public Object[][] createMatchingRuleInvalidAttributeValues() {
+        return new Object[][] { };
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @DataProvider(name = "matchingrules")
+    public Object[][] createMatchingRuleTest() {
+        return new Object[][] {
+            // \u00E9 = LATIN SMALL LETTER E WITH ACUTE
+            // \u00C9 = LATIN CAPITAL LETTER E WITH ACUTE
+            { "abc", "abd", ConditionResult.TRUE },
+            { "abc", "acc", ConditionResult.TRUE },
+            { "abc", "bbc", ConditionResult.TRUE },
+            { "abc", "abD", ConditionResult.TRUE },
+            { "def", "d\u00E9g", ConditionResult.TRUE },
+            { "def", "dEg", ConditionResult.TRUE },
+            { "dEf", "deg", ConditionResult.TRUE },
+            { "d\u00E9f", "dEg", ConditionResult.TRUE },
+            { "passe", "passe", ConditionResult.TRUE },
+            { "passe ", "passe", ConditionResult.TRUE },
+            { "passE", "passe", ConditionResult.TRUE },
+            { "pass\u00E9", "passe", ConditionResult.TRUE },
+            { "pass\u00E9", "passE", ConditionResult.TRUE },
+            { "pass\u00E9", "pass\u00C9", ConditionResult.TRUE },
+            { "passe", "pass\u00E9", ConditionResult.TRUE },
+            { "passE", "pass\u00E9", ConditionResult.TRUE },
+            { "pass\u00C9", "pass\u00E9", ConditionResult.TRUE },
+            { "abd", "abc", ConditionResult.FALSE },
+            { "acc", "abc", ConditionResult.FALSE },
+            { "bbc", "abc", ConditionResult.FALSE },
+            { "abD", "abc", ConditionResult.FALSE },
+            { "d\u00E9g", "def", ConditionResult.FALSE },
+            { "dEg", "def", ConditionResult.FALSE },
+            { "deg", "dEf", ConditionResult.FALSE },
+            { "dEg", "d\u00E9f", ConditionResult.FALSE },
+        };
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected MatchingRule getRule() {
+        // Note that oid and names are not used by the test (ie, they could be any value, test should pass anyway)
+        // Only the implementation class and the provided locale are really tested here.
+        String oid = "1.3.6.1.4.1.42.2.27.9.4.76.1.2";
+        Schema schema = new SchemaBuilder(Schema.getCoreSchema()).
+            buildMatchingRule(oid).
+                syntaxOID(SchemaConstants.SYNTAX_DIRECTORY_STRING_OID).
+                names("fr.lte").
+                implementation(CollationMatchingRulesImpl.lessThanOrEqualMatchingRule(new Locale("fr"))).
+                addToSchema().
+            toSchema();
+        return schema.getMatchingRule(oid);
+    }
+
+    @Test
+    public void testCreateIndexQuery() throws Exception {
+        ByteString value = ByteString.valueOf("abc");
+        MatchingRule matchingRule = getRule();
+        Assertion assertion = matchingRule.getAssertion(value);
+
+        String indexQuery = assertion.createIndexQuery(new FakeIndexQueryFactory(newIndexingOptions(), false));
+
+        ByteString normalizedValue = matchingRule.normalizeAttributeValue(value);
+        assertEquals(indexQuery, "rangeMatch(fr.shared, '' < value <= '" + normalizedValue.toHexString() + "')");
+    }
+}
diff --git a/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationSubstringMatchingRuleTest.java b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationSubstringMatchingRuleTest.java
new file mode 100644
index 0000000..3244d22
--- /dev/null
+++ b/opendj-sdk/opendj-core/src/test/java/org/forgerock/opendj/ldap/schema/CollationSubstringMatchingRuleTest.java
@@ -0,0 +1,187 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2014 ForgeRock AS.
+ */
+package org.forgerock.opendj.ldap.schema;
+
+import java.util.Locale;
+
+import org.forgerock.opendj.ldap.Assertion;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.ConditionResult;
+import org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.FakeIndexQueryFactory;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static org.forgerock.opendj.ldap.schema.AbstractSubstringMatchingRuleImplTest.*;
+import static org.testng.Assert.*;
+
+@SuppressWarnings("javadoc")
+@Test
+public class CollationSubstringMatchingRuleTest extends SubstringMatchingRuleTest {
+
+    @DataProvider(name = "substringInvalidAssertionValues")
+    public Object[][] createMatchingRuleInvalidAssertionValues() {
+        return new Object[][] { };
+    }
+
+    @DataProvider(name = "substringInvalidAttributeValues")
+    public Object[][] createMatchingRuleInvalidAttributeValues() {
+        return new Object[][] { };
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @DataProvider(name = "substringFinalMatchData")
+    public Object[][] createSubstringFinalMatchData() {
+        return new Object[][] {
+            { "this is a value", "value", ConditionResult.TRUE },
+            { "this is a value", "alue", ConditionResult.TRUE },
+            { "this is a value", "ue", ConditionResult.TRUE },
+            { "this is a value", "e", ConditionResult.TRUE },
+            { "this is a value", "valu", ConditionResult.FALSE },
+            { "this is a value", "this", ConditionResult.FALSE },
+            { "this is a value", "VALUE", ConditionResult.TRUE },
+            { "this is a value", "AlUe", ConditionResult.TRUE },
+            { "this is a value", "UE", ConditionResult.TRUE },
+            { "this is a value", "E", ConditionResult.TRUE },
+            { "this is a value", "THIS", ConditionResult.FALSE },
+            { "this is a value", " ", ConditionResult.TRUE },
+            { "this is a VALUE", "value", ConditionResult.TRUE },
+            { "end with space    ", " ", ConditionResult.TRUE },
+            { "end with space    ", "space", ConditionResult.TRUE },
+            { "end with space    ", "SPACE", ConditionResult.TRUE },
+            // \u00E9 = LATIN SMALL LETTER E WITH ACUTE
+            // \u00C9 = LATIN CAPITAL LETTER E WITH ACUTE
+            { "il est passe", "passE", ConditionResult.TRUE },
+            { "il est passe", "pass\u00E9", ConditionResult.TRUE },
+            { "il est passe", "pass\u00C9", ConditionResult.TRUE },
+            { "il est pass\u00C9", "passe", ConditionResult.TRUE },
+            { "il est pass\u00E9", "passE", ConditionResult.TRUE },
+        };
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @DataProvider(name = "substringInitialMatchData")
+    public Object[][] createSubstringInitialMatchData() {
+        return new Object[][] {
+            { "this is a value", "this", ConditionResult.TRUE },
+            { "this is a value", "th", ConditionResult.TRUE },
+            { "this is a value", "t", ConditionResult.TRUE },
+            { "this is a value", "is", ConditionResult.FALSE },
+            { "this is a value", "a", ConditionResult.FALSE },
+            { "this is a value", "TH", ConditionResult.TRUE },
+            { "this is a value", "T", ConditionResult.TRUE },
+            { "this is a value", "IS", ConditionResult.FALSE },
+            { "this is a value", "A", ConditionResult.FALSE },
+            { "this is a value", "VALUE", ConditionResult.FALSE },
+            { "this is a value", " ", ConditionResult.TRUE },
+            { "this is a value", "NOT", ConditionResult.FALSE },
+            { "this is a value", "THIS", ConditionResult.TRUE },
+            // \u00E9 = LATIN SMALL LETTER E WITH ACUTE
+            // \u00C9 = LATIN CAPITAL LETTER E WITH ACUTE
+            { "il etait passe", "Il \u00E9", ConditionResult.TRUE },
+            { "il etait passe", "Il \u00C9", ConditionResult.TRUE },
+            { "il etait passe", "Il E", ConditionResult.TRUE },
+            { "il \u00E9tait passe", "IL e", ConditionResult.TRUE },
+        };
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @DataProvider(name = "substringMiddleMatchData")
+    public Object[][] createSubstringMiddleMatchData() {
+        return new Object[][] {
+            { "this is a value", strings("this"), ConditionResult.TRUE },
+            { "this is a value", strings("is"), ConditionResult.TRUE },
+            { "this is a value", strings("a"), ConditionResult.TRUE },
+            { "this is a value", strings("value"), ConditionResult.TRUE },
+            { "this is a value", strings("THIS"), ConditionResult.TRUE },
+            { "this is a value", strings("IS"), ConditionResult.TRUE },
+            { "this is a value", strings("A"), ConditionResult.TRUE },
+            { "this is a value", strings("VALUE"), ConditionResult.TRUE },
+            { "this is a value", strings(" "), ConditionResult.TRUE },
+            { "this is a value", strings("this", "is", "a", "value"), ConditionResult.TRUE },
+            // The matching rule requires ordered non overlapping substrings.
+            // Issue #730 was not valid.
+            { "this is a value", strings("value", "this"), ConditionResult.FALSE },
+            { "this is a value", strings("this", "this is"), ConditionResult.FALSE },
+            { "this is a value", strings("this", "IS", "a", "VALue"), ConditionResult.TRUE },
+            { "this is a value", strings("his IS", "A val"), ConditionResult.TRUE },
+            { "this is a value", strings("not"), ConditionResult.FALSE },
+            { "this is a value", strings("this", "not"), ConditionResult.FALSE },
+            { "this is a value", strings("    "), ConditionResult.TRUE },
+            // \u00E9 = LATIN SMALL LETTER E WITH ACUTE
+            // \u00C9 = LATIN CAPITAL LETTER E WITH ACUTE
+            { "il est passe par la", strings("est", "pass\u00E9"), ConditionResult.TRUE },
+            { "il est passe par la", strings("pass\u00E9", "Par"), ConditionResult.TRUE },
+            { "il est passe par la", strings("est", "pass\u00C9", "PAR", "La"), ConditionResult.TRUE },
+            { "il est pass\u00E9 par la", strings("il", "Est", "pass\u00C9"), ConditionResult.TRUE },
+            { "il est pass\u00C9 par la", strings("est", "passe"), ConditionResult.TRUE },
+        };
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected MatchingRule getRule() {
+        // Note that oid and names are not used by the test (ie, they could be any value, test should pass anyway)
+        // Only the implementation class and the provided locale are really tested here.
+        String oid = "1.3.6.1.4.1.42.2.27.9.4.76.1.6";
+        Schema schema = new SchemaBuilder(Schema.getCoreSchema()).
+            buildMatchingRule(oid).
+                syntaxOID(SchemaConstants.SYNTAX_DIRECTORY_STRING_OID).
+                names("fr.sub").
+                implementation(CollationMatchingRulesImpl.substringMatchingRule(new Locale("fr"))).
+                addToSchema().
+            toSchema();
+        return schema.getMatchingRule(oid);
+    }
+
+    @Test
+    public void testCreateIndexQuery() throws Exception {
+        ByteString value = ByteString.valueOf("a*c");
+        MatchingRule matchingRule = getRule();
+        Assertion assertion = matchingRule.getAssertion(value);
+
+        String indexQuery = assertion.createIndexQuery(new FakeIndexQueryFactory(newIndexingOptions(), false));
+
+        ByteString binit = matchingRule.normalizeAttributeValue(ByteString.valueOf("a"));
+        ByteString bfinal = matchingRule.normalizeAttributeValue(ByteString.valueOf("c"));
+        assertEquals(indexQuery,
+            "intersect["
+            + "rangeMatch(fr.shared, '" + binit.toHexString() + "' <= value < '00 54'), "
+            + "rangeMatch(fr.substring, '" + bfinal.toHexString() + "' <= value < '00 56'), "
+            + "rangeMatch(fr.substring, '" + binit.toHexString() + "' <= value < '00 54')]"
+        );
+    }
+}

--
Gitblit v1.10.0