From e67f90aeff42363b892ae478f935b00c7e64e0a8 Mon Sep 17 00:00:00 2001
From: Jean-Noel Rouvignac <jean-noel.rouvignac@forgerock.com>
Date: Thu, 06 Mar 2014 13:00:09 +0000
Subject: [PATCH] OPENDJ-1308 (CR-3138) Migrate schema support

---
 opendj-sdk/opendj-core/clirr-ignored-api-changes.xml                                                              |   20 ++
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/IndexingOptions.java                           |   41 ++++
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/KeywordEqualityMatchingRuleImpl.java        |   13 +
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/CertificateExactMatchingRuleImpl.java       |    5 
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/Assertion.java                                     |   19 +
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractApproximateMatchingRuleImpl.java    |    8 
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractOrderingMatchingRuleImpl.java       |   26 ++
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractMatchingRuleImpl.java               |   44 ++++
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/EqualLengthApproximateMatchingRuleImpl.java |    6 
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/MatchingRuleImpl.java                       |   15 +
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractEqualityMatchingRuleImpl.java       |    8 
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/Indexer.java                                   |   69 ++++++
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/MatchingRule.java                           |   10 +
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/IndexQueryFactory.java                         |  117 +++++++++++
 opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractSubstringMatchingRuleImpl.java      |  145 +++++++++++++
 15 files changed, 536 insertions(+), 10 deletions(-)

diff --git a/opendj-sdk/opendj-core/clirr-ignored-api-changes.xml b/opendj-sdk/opendj-core/clirr-ignored-api-changes.xml
index e02b4ef..b7c314d 100644
--- a/opendj-sdk/opendj-core/clirr-ignored-api-changes.xml
+++ b/opendj-sdk/opendj-core/clirr-ignored-api-changes.xml
@@ -178,4 +178,24 @@
     <method>org.forgerock.opendj.ldap.LDAPOptions setDecodeOptions(org.forgerock.opendj.ldap.DecodeOptions)</method>
     <justification>OPENDJ-1197: Method return type has changed due to reification</justification>
   </difference>
+
+  <difference>
+    <className>org/forgerock/opendj/ldap/Assertion</className>
+    <differenceType>7012</differenceType>
+    <method>java.lang.Object createIndexQuery(org.forgerock.opendj.ldap.spi.IndexQueryFactory)</method>
+    <justification>OPENDJ-1308 Migrate schema support: allows decoupling indexing from a specific backend</justification>
+  </difference>
+  <difference>
+    <className>org/forgerock/opendj/ldap/schema/MatchingRuleImpl</className>
+    <differenceType>7012</differenceType>
+    <method>org.forgerock.opendj.ldap.spi.Indexer getIndexer()</method>
+    <justification>OPENDJ-1308 Migrate schema support: allows decoupling indexing from a specific backend</justification>
+  </difference>
+  <difference>
+    <className>org/forgerock/opendj/ldap/schema/MatchingRuleImpl</className>
+    <differenceType>7012</differenceType>
+    <method>boolean isIndexingSupported()</method>
+    <justification>OPENDJ-1308 Migrate schema support: allows decoupling indexing from a specific backend</justification>
+  </difference>
+
 </differences>
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/Assertion.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/Assertion.java
index e736b20..59ebf9a 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/Assertion.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/Assertion.java
@@ -26,10 +26,13 @@
  */
 package org.forgerock.opendj.ldap;
 
+import org.forgerock.opendj.ldap.spi.IndexQueryFactory;
+
 /**
  * A compiled attribute value assertion.
  */
 public interface Assertion {
+
     /**
      * Indicates whether the provided attribute value should be considered a
      * match for this assertion value according to the matching rule.
@@ -41,4 +44,20 @@
      *         match, or {@code UNDEFINED} if the result is undefined.
      */
     ConditionResult matches(ByteSequence normalizedAttributeValue);
+
+    /**
+     * Returns an index query appropriate for the provided attribute
+     * value assertion.
+     *
+     * @param <T>
+     *          The type of index query created by the {@code factory}.
+     * @param factory
+     *          The index query factory which should be used to
+     *          construct the index query.
+     * @return The index query appropriate for the provided attribute
+     *         value assertion.
+     * @throws DecodeException
+     *           If an error occurs while generating the index query.
+     */
+    <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException;
 }
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractApproximateMatchingRuleImpl.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractApproximateMatchingRuleImpl.java
index b1f11ba..ab84c38 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractApproximateMatchingRuleImpl.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/AbstractApproximateMatchingRuleImpl.java
@@ -28,6 +28,7 @@
 import org.forgerock.opendj.ldap.Assertion;
 import org.forgerock.opendj.ldap.ByteSequence;
 import org.forgerock.opendj.ldap.DecodeException;
+import org.forgerock.opendj.ldap.spi.Indexer;
 
 /**
  * This class implements an approximate matching rule that matches normalized
@@ -35,6 +36,8 @@
  */
 abstract class AbstractApproximateMatchingRuleImpl extends AbstractMatchingRuleImpl {
 
+    private final Indexer indexer = new DefaultIndexer("approximate");
+
     AbstractApproximateMatchingRuleImpl() {
         // Nothing to do.
     }
@@ -45,4 +48,9 @@
         return DefaultAssertion.approximate(normalizeAttributeValue(schema, assertionValue));
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public Indexer getIndexer() {
+        return indexer;
+    }
 }
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 84d19ed..6744f3b 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
@@ -28,6 +28,7 @@
 import org.forgerock.opendj.ldap.Assertion;
 import org.forgerock.opendj.ldap.ByteSequence;
 import org.forgerock.opendj.ldap.DecodeException;
+import org.forgerock.opendj.ldap.spi.Indexer;
 
 /**
  * This class implements an equality matching rule that matches normalized
@@ -35,6 +36,8 @@
  */
 abstract class AbstractEqualityMatchingRuleImpl extends AbstractMatchingRuleImpl {
 
+    private final Indexer indexer = new DefaultIndexer("equality");
+
     AbstractEqualityMatchingRuleImpl() {
         // Nothing to do.
     }
@@ -45,4 +48,9 @@
         return DefaultAssertion.equality(normalizeAttributeValue(schema, assertionValue));
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public Indexer getIndexer() {
+        return indexer;
+    }
 }
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 5a60314..2dd602f 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
@@ -26,13 +26,18 @@
  */
 package org.forgerock.opendj.ldap.schema;
 
+import java.util.Collection;
 import java.util.Comparator;
 import java.util.List;
 
 import org.forgerock.opendj.ldap.Assertion;
 import org.forgerock.opendj.ldap.ByteSequence;
+import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.ConditionResult;
 import org.forgerock.opendj.ldap.DecodeException;
+import org.forgerock.opendj.ldap.spi.IndexQueryFactory;
+import org.forgerock.opendj.ldap.spi.Indexer;
+import org.forgerock.opendj.ldap.spi.IndexingOptions;
 
 /**
  * This class implements a default equality or approximate matching rule that
@@ -41,7 +46,7 @@
 abstract class AbstractMatchingRuleImpl implements MatchingRuleImpl {
 
     static final class DefaultAssertion implements Assertion {
-        /** The ID of the DB index that to use with this assertion.*/
+        /** The ID of the DB index to use with this assertion.*/
         private final String indexID;
         private final ByteSequence normalizedAssertionValue;
 
@@ -61,12 +66,42 @@
         public ConditionResult matches(final ByteSequence attributeValue) {
             return ConditionResult.valueOf(normalizedAssertionValue.equals(attributeValue));
         }
+
+        public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException {
+            return factory.createExactMatchQuery(indexID, normalizedAssertionValue);
+        }
     }
 
+    final class DefaultIndexer implements Indexer {
+        /** The ID of the DB index to use with this indexer.*/
+        private final String indexID;
+
+        DefaultIndexer(String indexID) {
+            this.indexID = indexID;
+        }
+
+        @Override
+        public void createKeys(Schema schema, ByteSequence value, IndexingOptions options, Collection<ByteString> keys)
+                throws DecodeException {
+            keys.add(normalizeAttributeValue(schema, value));
+        }
+
+        @Override
+        public String getIndexID() {
+            return indexID;
+        }
+    };
+
     private static final Assertion UNDEFINED_ASSERTION = new Assertion() {
         public ConditionResult matches(final ByteSequence attributeValue) {
             return ConditionResult.UNDEFINED;
         }
+
+        public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException {
+            // Subclassing this class will always work, albeit inefficiently.
+            // This is better than throwing an exception for no good reason.
+            return factory.createMatchAllQuery();
+        }
     };
 
     private static final Comparator<ByteSequence> DEFAULT_COMPARATOR =
@@ -104,4 +139,11 @@
             throws DecodeException {
         return UNDEFINED_ASSERTION;
     }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isIndexingSupported() {
+        return getIndexer() != null;
+    }
+
 }
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 5d8a171..741172b 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
@@ -31,12 +31,17 @@
 import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.ConditionResult;
 import org.forgerock.opendj.ldap.DecodeException;
+import org.forgerock.opendj.ldap.spi.IndexQueryFactory;
+import org.forgerock.opendj.ldap.spi.Indexer;
 
 /**
  * This class implements a default ordering matching rule that matches
  * normalized values in byte order.
  */
 abstract class AbstractOrderingMatchingRuleImpl extends AbstractMatchingRuleImpl {
+
+    private final Indexer indexer = new DefaultIndexer("ordering");
+
     AbstractOrderingMatchingRuleImpl() {
         // Nothing to do.
     }
@@ -48,6 +53,11 @@
             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("ordering", ByteString.empty(), normAssertion, false, false);
+            }
         };
     }
 
@@ -59,6 +69,11 @@
             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("ordering", normAssertion, ByteString.empty(), true, false);
+            }
         };
     }
 
@@ -70,6 +85,17 @@
             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("ordering", ByteString.empty(), normAssertion, false, true);
+            }
         };
     }
+
+    /** {@inheritDoc} */
+    @Override
+    public Indexer getIndexer() {
+        return indexer;
+    }
 }
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 c04b5e1..c825794 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
@@ -28,8 +28,10 @@
 
 import static com.forgerock.opendj.ldap.CoreMessages.*;
 
+import java.util.Collection;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.TreeSet;
 
 import org.forgerock.i18n.LocalizableMessage;
 import org.forgerock.opendj.ldap.Assertion;
@@ -38,6 +40,9 @@
 import org.forgerock.opendj.ldap.ByteStringBuilder;
 import org.forgerock.opendj.ldap.ConditionResult;
 import org.forgerock.opendj.ldap.DecodeException;
+import org.forgerock.opendj.ldap.spi.IndexQueryFactory;
+import org.forgerock.opendj.ldap.spi.Indexer;
+import org.forgerock.opendj.ldap.spi.IndexingOptions;
 
 import com.forgerock.opendj.util.SubstringReader;
 
@@ -46,18 +51,34 @@
  * normalized substring assertion values in byte order.
  */
 abstract class AbstractSubstringMatchingRuleImpl extends AbstractMatchingRuleImpl {
-    static class DefaultSubstringAssertion implements Assertion {
+
+    /**
+     * Default assertion implementation for substring matching rules.
+     * For example, with the assertion value "initial*any1*any2*any3*final",
+     * the assertion will be decomposed like this:
+     * <ul>
+     * <li>normInitial will contain "initial"</li>
+     * <li>normAnys will contain [ "any1", "any2", "any3" ]</li>
+     * <li>normFinal will contain "final"</li>
+     * </ul>
+     */
+    static 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. */
         private final ByteString[] normAnys;
+        /** Normalized substring for the text after the last '*' character. */
         private final ByteString normFinal;
 
-        protected DefaultSubstringAssertion(final ByteString normInitial,
+        private DefaultSubstringAssertion(final ByteString normInitial,
                 final ByteString[] normAnys, final ByteString normFinal) {
             this.normInitial = normInitial;
             this.normAnys = normAnys;
             this.normFinal = normFinal;
         }
 
+        /** {@inheritDoc} */
+        @Override
         public ConditionResult matches(final ByteSequence attributeValue) {
             final int valueLength = attributeValue.length();
 
@@ -125,12 +146,118 @@
 
             return ConditionResult.TRUE;
         }
+
+        /** {@inheritDoc} */
+        @Override
+        public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException {
+            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));
+            }
+            if (normAnys != null) {
+                for (ByteString normAny : normAnys) {
+                    substringMatch(factory, normAny, subqueries);
+                }
+            }
+            if (normFinal != null) {
+                substringMatch(factory, normFinal, subqueries);
+            }
+            if (normInitial != null) {
+                // Add this one last to minimize the risk to run the same search twice
+                // (possible overlapping with the use of equality index at the start of this method)
+                substringMatch(factory, normInitial, subqueries);
+            }
+            return factory.createIntersectionQuery(subqueries);
+        }
+
+        private <T> T rangeMatch(IndexQueryFactory<T> factory, String indexID, ByteSequence lower) {
+            // Iterate through all the keys that have this value as the prefix.
+
+            // Set the upper bound for a range search.
+            // We need a key for the upper bound that is of equal length
+            // but slightly greater than the lower bound.
+            final ByteStringBuilder upper = new ByteStringBuilder(lower);
+
+            for (int i = upper.length() - 1; i >= 0; i--) {
+                if (upper.byteAt(i) == 0xFF) {
+                    // We have to carry the overflow to the more significant byte.
+                    upper.setByte(i, (byte) 0);
+                } else {
+                    // No overflow, we can stop.
+                    upper.setByte(i, (byte) (upper.byteAt(i) + 1));
+                    break;
+                }
+            }
+
+            // Read the range: lower <= keys < upper.
+            return factory.createRangeMatchQuery(indexID, lower, upper, true, false);
+        }
+
+        private <T> void substringMatch(final IndexQueryFactory<T> factory, final ByteString normSubstring,
+                final Collection<T> subqueries) {
+            int substrLength = factory.getIndexingOptions().substringKeySize();
+
+            // 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));
+            } else {
+                // Break the value up into fragments of length equal to the
+                // index substring length, and read those keys.
+
+                // Eliminate duplicates by putting the keys into a set.
+                final TreeSet<ByteSequence> substringKeys = new TreeSet<ByteSequence>();
+
+                // Example: The value is ABCDE and the substring length is 3.
+                // We produce the keys ABC BCD CDE.
+                for (int first = 0, last = substrLength;
+                     last <= normSubstring.length(); first++, last++) {
+                    substringKeys.add(normSubstring.subSequence(first, first + substrLength));
+                }
+
+                for (ByteSequence key : substringKeys) {
+                    subqueries.add(factory.createExactMatchQuery("substring", key));
+                }
+            }
+        }
+
     }
 
+    final class SubstringIndexer implements Indexer {
+
+        /** {@inheritDoc} */
+        @Override
+        public void createKeys(Schema schema, ByteSequence value, IndexingOptions options, Collection<ByteString> keys)
+                throws DecodeException {
+            final ByteString normValue = normalizeAttributeValue(schema, value);
+            final int substringKeySize = options.substringKeySize();
+
+            // Example: The value is ABCDE and the substring length is 3.
+            // We produce the keys ABC BCD CDE DE E
+            // To find values containing a short substring such as DE,
+            // iterate through keys with prefix DE. To find values
+            // containing a longer substring such as BCDE, read keys BCD and CDE.
+            for (int i = 0, remain = normValue.length(); remain > 0; i++, remain--) {
+                int len = Math.min(substringKeySize, remain);
+                keys.add(normValue.subSequence(i, i  + len));
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public String getIndexID() {
+            return "substring";
+        }
+    }
+
+    private SubstringIndexer substringIndexer = new SubstringIndexer();
+
     AbstractSubstringMatchingRuleImpl() {
         // Nothing to do.
     }
 
+    /** {@inheritDoc} */
     @Override
     public Assertion getAssertion(final Schema schema, final ByteSequence assertionValue)
             throws DecodeException {
@@ -143,7 +270,6 @@
         List<ByteSequence> anyStrings = null;
 
         final String valueString = assertionValue.toString();
-
         if (valueString.length() == 1 && valueString.charAt(0) == '*') {
             return getSubstringAssertion(schema, initialString, anyStrings, finalString);
         }
@@ -156,8 +282,7 @@
             initialString = normalizeSubString(schema, bytes);
         }
         if (reader.remaining() == 0) {
-            throw DecodeException.error(WARN_ATTR_SYNTAX_SUBSTRING_NO_WILDCARDS.get(assertionValue
-                    .toString()));
+            throw DecodeException.error(WARN_ATTR_SYNTAX_SUBSTRING_NO_WILDCARDS.get(assertionValue));
         }
         while (true) {
             reader.read();
@@ -165,7 +290,7 @@
             if (reader.remaining() > 0) {
                 if (bytes.length() == 0) {
                     throw DecodeException.error(WARN_ATTR_SYNTAX_SUBSTRING_CONSECUTIVE_WILDCARDS
-                            .get(assertionValue.toString(), reader.pos()));
+                            .get(assertionValue, reader.pos()));
                 }
                 if (anyStrings == null) {
                     anyStrings = new LinkedList<ByteSequence>();
@@ -182,6 +307,7 @@
         return getSubstringAssertion(schema, initialString, anyStrings, finalString);
     }
 
+    /** {@inheritDoc} */
     @Override
     public Assertion getSubstringAssertion(final Schema schema, final ByteSequence subInitial,
             final List<? extends ByteSequence> subAnyElements, final ByteSequence subFinal)
@@ -436,4 +562,11 @@
             return ByteString.empty();
         }
     }
+
+    /** {@inheritDoc} */
+    @Override
+    public Indexer getIndexer() {
+        return substringIndexer;
+    }
+
 }
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/CertificateExactMatchingRuleImpl.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/CertificateExactMatchingRuleImpl.java
index 1a795f9..2ea67c6 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/CertificateExactMatchingRuleImpl.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/CertificateExactMatchingRuleImpl.java
@@ -23,7 +23,6 @@
  *
  *      Copyright 2006-2009 Sun Microsystems, Inc.
  *      Portions Copyright 2013-2014 Manuel Gaupp
- *      Copyright 2014 ForgeRock AS
  */
 package org.forgerock.opendj.ldap.schema;
 
@@ -43,8 +42,8 @@
 import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.ByteStringBuilder;
 import org.forgerock.opendj.ldap.DecodeException;
-import org.forgerock.opendj.ldap.GSERParser;
 import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.GSERParser;
 
 import com.forgerock.opendj.util.StaticUtils;
 
@@ -55,7 +54,7 @@
  * X.509 and referenced in RFC 4523.
  */
 final class CertificateExactMatchingRuleImpl
-        extends AbstractMatchingRuleImpl {
+        extends AbstractEqualityMatchingRuleImpl {
 
     private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
 
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/EqualLengthApproximateMatchingRuleImpl.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/EqualLengthApproximateMatchingRuleImpl.java
index 8a16d83..9583da2 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/EqualLengthApproximateMatchingRuleImpl.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/EqualLengthApproximateMatchingRuleImpl.java
@@ -31,6 +31,7 @@
 import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.ConditionResult;
 import org.forgerock.opendj.ldap.DecodeException;
+import org.forgerock.opendj.ldap.spi.IndexQueryFactory;
 
 /**
  * This class implements an extremely simple approximate matching rule that will
@@ -46,6 +47,11 @@
             public ConditionResult matches(final ByteSequence attributeValue) {
                 return ConditionResult.valueOf(attributeValue.length() == assertionValue.length());
             }
+
+            @Override
+            public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException {
+                return factory.createExactMatchQuery("approximate", ByteString.valueOf(assertionValue.length()));
+            }
         };
     }
 
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/KeywordEqualityMatchingRuleImpl.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/KeywordEqualityMatchingRuleImpl.java
index 8a33b76..8bbdbd8 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/KeywordEqualityMatchingRuleImpl.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/KeywordEqualityMatchingRuleImpl.java
@@ -34,6 +34,8 @@
 import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.ConditionResult;
 import org.forgerock.opendj.ldap.DecodeException;
+import org.forgerock.opendj.ldap.spi.IndexQueryFactory;
+import org.forgerock.opendj.ldap.spi.Indexer;
 
 /**
  * This class implements the keywordMatch matching rule defined in X.520. That
@@ -107,9 +109,20 @@
                     return false;
                 }
             }
+
+            @Override
+            public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException {
+                return factory.createMatchAllQuery();
+            }
         };
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public Indexer getIndexer() {
+        return null;
+    }
+
     public ByteString normalizeAttributeValue(final Schema schema, final ByteSequence value) {
         return ByteString.valueOf(normalize(value));
     }
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/MatchingRule.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/MatchingRule.java
index 0e7bdcc..a6a6dcb 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/MatchingRule.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/MatchingRule.java
@@ -43,6 +43,7 @@
 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;
 
 
 /**
@@ -361,6 +362,15 @@
     }
 
     /**
+     * Returns the indexer for this matching rule.
+     *
+     * @return the indexer for this matching rule.
+     */
+    public Indexer getIndexer() {
+        return impl.getIndexer();
+    }
+
+    /**
      * Returns the name or OID for this schema definition. If it has one or more
      * names, then the primary name will be returned. If it does not have any
      * names, then the OID will be returned.
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/MatchingRuleImpl.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/MatchingRuleImpl.java
index 28e02a0..bf74e88 100644
--- a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/MatchingRuleImpl.java
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/schema/MatchingRuleImpl.java
@@ -33,6 +33,7 @@
 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;
 
 /**
  * This interface defines the set of methods that must be implemented to define
@@ -139,4 +140,18 @@
     ByteString normalizeAttributeValue(Schema schema, ByteSequence value)
             throws DecodeException;
 
+    /**
+     * Returns the indexer for this matching rule.
+     *
+     * @return the indexer for this matching rule.
+     */
+    Indexer getIndexer();
+
+    /**
+     * Returns whether a backend can build an index for this matching rule.
+     *
+     * @return true a backend can build an index for this matching rule,
+     *         false otherwise.
+     */
+    boolean isIndexingSupported();
 }
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/IndexQueryFactory.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/IndexQueryFactory.java
new file mode 100644
index 0000000..346c115
--- /dev/null
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/IndexQueryFactory.java
@@ -0,0 +1,117 @@
+/*
+ * 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 2009 Sun Microsystems, Inc.
+ *      Portions Copyright 2014 ForgeRock AS
+ */
+package org.forgerock.opendj.ldap.spi;
+
+import java.util.Collection;
+
+import org.forgerock.opendj.ldap.ByteSequence;
+
+/**
+ * A factory for creating arbitrarily complex index queries. This
+ * interface is implemented by the underlying backend implementation
+ * and passed to extensible matching rules so that they can construct
+ * arbitrarily complex index queries.
+ *
+ * @param <T>
+ *          The type of query created by this factory.
+ */
+public interface IndexQueryFactory<T> {
+
+    /**
+     * Returns a query requesting an index record matching the provided key.
+     *
+     * @param indexID
+     *          An identifier of the index type.
+     * @param key
+     *          A byte sequence containing the key.
+     * @return A query requesting the index record matching the key.
+     */
+    T createExactMatchQuery(String indexID, ByteSequence key);
+
+    /**
+     * Returns a query requesting all index records. A backend implementation
+     * may choose to return all or no records as part of the optimization.
+     *
+     * @return A query requesting all index records.
+     */
+    T createMatchAllQuery();
+
+    /**
+     * Returns a query requesting all index records in the specified range.
+     *
+     * @param indexID
+     *          An identifier of the index type.
+     * @param lower
+     *          The lower bound of the range. A 0 length byte array indicates no
+     *          lower bound and the range will start from the smallest key.
+     * @param upper
+     *          The upper bound of the range. A 0 length byte array indicates no
+     *          upper bound and the range will end at the largest key.
+     * @param lowerIncluded
+     *          true if a key exactly matching the lower bound is included in
+     *          the range, false if only keys strictly greater than the lower
+     *          bound are included. This value is ignored if the lower bound is
+     *          not specified.
+     * @param upperIncluded
+     *          true if a key exactly matching the upper bound is included in
+     *          the range, false if only keys strictly less than the upper bound
+     *          are included. This value is ignored if the upper bound is not
+     *          specified.
+     * @return A query requesting all index records in the specified range.
+     */
+    T createRangeMatchQuery(String indexID, ByteSequence lower,
+            ByteSequence upper, boolean lowerIncluded, boolean upperIncluded);
+
+    /**
+     * Returns a query which returns the intersection of a collection of
+     * sub-queries.
+     *
+     * @param subqueries
+     *          A collection of sub-queries.
+     * @return A query which returns the intersection of a collection of
+     *         sub-queries.
+     */
+    T createIntersectionQuery(Collection<T> subqueries);
+
+    /**
+     * Returns a query which combines the results of a collection of
+     * sub-queries.
+     *
+     * @param subqueries
+     *          A collection of sub-queries.
+     * @return A query which combines the results of a collection of
+     *         sub-queries.
+     */
+    T createUnionQuery(Collection<T> subqueries);
+
+    /**
+     * Returns the indexing options for this factory.
+     *
+     * @return the indexing options for this factory.
+     */
+    IndexingOptions getIndexingOptions();
+}
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/Indexer.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/Indexer.java
new file mode 100644
index 0000000..b00b1ed
--- /dev/null
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/Indexer.java
@@ -0,0 +1,69 @@
+/*
+ * 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 2009 Sun Microsystems, Inc.
+ */
+package org.forgerock.opendj.ldap.spi;
+
+import java.util.Collection;
+
+import org.forgerock.opendj.ldap.ByteSequence;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.DecodeException;
+import org.forgerock.opendj.ldap.schema.Schema;
+
+/**
+ * This class is registered with a Backend and it provides call- backs
+ * for indexing attribute values. An index implementation will use
+ * this interface to create the keys for an attribute value.
+ */
+public interface Indexer {
+
+    /**
+     * Returns an index identifier associated with this indexer. An identifier
+     * should be selected based on the matching rule type. A unique identifier
+     * will map to a unique index database in the backend implementation. If
+     * multiple matching rules need to share the index database, the
+     * corresponding indexers should always use the same identifier.
+     *
+     * @return index ID A String containing the ID associated with this indexer.
+     */
+    String getIndexID();
+
+    /**
+     * Generates the set of index keys for an attribute.
+     *
+     * @param schema
+     *          The schema in which the associated matching rule is defined.
+     * @param value
+     *          The attribute value for which keys are required.
+     * @param options
+     *          The indexing options
+     * @param keys
+     *          A collection where to add the created keys.
+     * @throws DecodeException if an error occurs while normalizing the value
+     */
+    void createKeys(Schema schema, ByteSequence value, IndexingOptions options,
+            Collection<ByteString> keys) throws DecodeException;
+
+}
diff --git a/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/IndexingOptions.java b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/IndexingOptions.java
new file mode 100644
index 0000000..24be72f
--- /dev/null
+++ b/opendj-sdk/opendj-core/src/main/java/org/forgerock/opendj/ldap/spi/IndexingOptions.java
@@ -0,0 +1,41 @@
+/*
+ * 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.spi;
+
+/**
+ * Contains options indicating how indexing must be performed.
+ */
+public interface IndexingOptions {
+
+    /**
+     * Returns the maximum size to use when building the keys for the
+     * "substring" index.
+     *
+     * @return the maximum size to use when building the keys for the
+     *         "substring" index.
+     */
+    int substringKeySize();
+
+}

--
Gitblit v1.10.0