From 3dc3f07607a5c720a1452cc75701554a9f3ad715 Mon Sep 17 00:00:00 2001
From: Nicolas Capponi <nicolas.capponi@forgerock.com>
Date: Mon, 24 Mar 2014 09:45:42 +0000
Subject: [PATCH] Checkpoint commit for OPENDJ-1308 Migrate schema support

---
 opendj3-server-dev/src/server/org/opends/server/api/VirtualAttributeProvider.java                                          |   87 ----
 opendj3-server-dev/src/server/org/opends/server/api/MatchingRule.java                                                      |   22 +
 opendj3-server-dev/src/server/org/opends/server/types/AttributeBuilder.java                                                |   81 ----
 opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/CaseExactIA5SubstringMatchingRuleTest.java  |   33 +
 opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/SubstringMatchingRuleTest.java              |   35 +
 opendj3-server-dev/src/server/org/opends/server/api/AbstractMatchingRule.java                                              |   11 
 opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/CaseExactSubstringMatchingRuleTest.java     |   33 +
 opendj3-server-dev/src/server/org/opends/server/types/SearchFilter.java                                                    |  258 ++++---------
 opendj3-server-dev/build.xml                                                                                               |    2 
 opendj3-server-dev/src/server/org/opends/server/controls/MatchedValuesFilter.java                                          |  157 +-------
 opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/CaseIgnoreSubstringMatchingRuleTest.java    |   33 +
 opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/CaseIgnoreIA5SubstringMatchingRuleTest.java |   33 +
 opendj3-server-dev/src/server/org/opends/server/api/SubstringMatchingRule.java                                             |  260 ++++++++++++++
 opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/NumericStringSubstringMatchingRuleTest.java |   25 +
 14 files changed, 603 insertions(+), 467 deletions(-)

diff --git a/opendj3-server-dev/build.xml b/opendj3-server-dev/build.xml
index ed15780..b32cb07 100644
--- a/opendj3-server-dev/build.xml
+++ b/opendj3-server-dev/build.xml
@@ -35,6 +35,7 @@
 
   <!-- Build JVM properties -->
   <property name="MEM" value="512M"/>
+  <property name="PERMSIZE" value="128M"/>
 
   <!-- Build OpenDMK properties -->
   <property file="build.properties"/>
@@ -2403,6 +2404,7 @@
       <jvmarg value="-Dtest.progress=${test.progress}" />
       <jvmarg value="-Xms${MEM}" />
       <jvmarg value="-Xmx${MEM}" />
+      <jvmarg value="-XX:MaxPermSize=${PERMSIZE}" />
       <jvmarg value="${jvm.debug.arg1}" />
       <jvmarg value="${jvm.debug.arg2}" />
       <jvmarg value="${jvm.debug.arg3}" />
diff --git a/opendj3-server-dev/src/server/org/opends/server/api/AbstractMatchingRule.java b/opendj3-server-dev/src/server/org/opends/server/api/AbstractMatchingRule.java
index 5a1605d..c377a94 100644
--- a/opendj3-server-dev/src/server/org/opends/server/api/AbstractMatchingRule.java
+++ b/opendj3-server-dev/src/server/org/opends/server/api/AbstractMatchingRule.java
@@ -28,6 +28,7 @@
 
 import java.util.Collection;
 import java.util.Comparator;
+import java.util.List;
 
 import org.forgerock.opendj.ldap.Assertion;
 import org.forgerock.opendj.ldap.ByteSequence;
@@ -148,6 +149,16 @@
 
   /** {@inheritDoc} */
   @Override
+  public Assertion getSubstringAssertion(ByteSequence subInitial,
+      List<? extends ByteSequence> subAnyElements, ByteSequence subFinal) throws DecodeException
+  {
+    return UNDEFINED_ASSERTION;
+  }
+
+
+
+  /** {@inheritDoc} */
+  @Override
   public boolean isObsolete()
   {
     return false;
diff --git a/opendj3-server-dev/src/server/org/opends/server/api/MatchingRule.java b/opendj3-server-dev/src/server/org/opends/server/api/MatchingRule.java
index be53226..0d3c9ee 100644
--- a/opendj3-server-dev/src/server/org/opends/server/api/MatchingRule.java
+++ b/opendj3-server-dev/src/server/org/opends/server/api/MatchingRule.java
@@ -28,6 +28,7 @@
 
 import java.util.Collection;
 import java.util.Comparator;
+import java.util.List;
 
 import org.forgerock.opendj.ldap.Assertion;
 import org.forgerock.opendj.ldap.ByteSequence;
@@ -148,6 +149,27 @@
   public Assertion getLessOrEqualAssertion(final ByteSequence assertionValue) throws DecodeException;
 
   /**
+   * Returns the normalized form of the provided assertion substring values,
+   * which is best suited for efficiently performing matching operations on
+   * that value.
+   *
+   * @param subInitial
+   *            The normalized substring value fragment that should appear at
+   *            the beginning of the target value.
+   * @param subAnyElements
+   *            The normalized substring value fragments that should appear in
+   *            the middle of the target value.
+   * @param subFinal
+   *            The normalized substring value fragment that should appear at
+   *            the end of the target value.
+   * @return The normalized version of the provided assertion value.
+   * @throws DecodeException
+   *             if the syntax of the value is not valid.
+   */
+  public Assertion getSubstringAssertion(final ByteSequence subInitial,
+      final List<? extends ByteSequence> subAnyElements, final ByteSequence subFinal) throws DecodeException;
+
+  /**
    * Indicates whether this matching rule is declared "OBSOLETE". The
    * default implementation will always return {@code false}. If that
    * is not acceptable for a particular matching rule implementation,
diff --git a/opendj3-server-dev/src/server/org/opends/server/api/SubstringMatchingRule.java b/opendj3-server-dev/src/server/org/opends/server/api/SubstringMatchingRule.java
index 7984df1..b3d84fb 100644
--- a/opendj3-server-dev/src/server/org/opends/server/api/SubstringMatchingRule.java
+++ b/opendj3-server-dev/src/server/org/opends/server/api/SubstringMatchingRule.java
@@ -26,11 +26,18 @@
  */
 package org.opends.server.api;
 
+import java.util.Collection;
+import java.util.LinkedList;
 import java.util.List;
+import java.util.TreeSet;
 
+import org.forgerock.opendj.ldap.Assertion;
 import org.forgerock.opendj.ldap.ByteSequence;
 import org.forgerock.opendj.ldap.ByteString;
+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;
 
 /**
  * This class defines the set of methods and structures that must be
@@ -175,5 +182,258 @@
 
     return true;
   }
+
+  /**
+   * 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;
+
+      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 normalizedAttributeValue) {
+          final int valueLength = normalizedAttributeValue.length();
+
+          int pos = 0;
+          if (normInitial != null) {
+              final int initialLength = normInitial.length();
+              if (initialLength > valueLength) {
+                  return ConditionResult.FALSE;
+              }
+
+              for (; pos < initialLength; pos++) {
+                  if (normInitial.byteAt(pos) != normalizedAttributeValue.byteAt(pos)) {
+                      return ConditionResult.FALSE;
+                  }
+              }
+          }
+
+          if (normAnys != null) {
+          matchEachSubstring:
+              for (final ByteSequence element : normAnys) {
+                  final int anyLength = element.length();
+                  final int end = valueLength - anyLength;
+              matchCurrentSubstring:
+                  for (; pos <= end; pos++) {
+                      // Try to match all characters from the substring
+                      for (int i = 0; i < anyLength; i++) {
+                          if (element.byteAt(i) != normalizedAttributeValue.byteAt(pos + i)) {
+                              // not a match,
+                              // try to find a match in the rest of this value
+                              continue matchCurrentSubstring;
+                          }
+                      }
+                      // we just matched current substring,
+                      // go try to match the next substring
+                      pos += anyLength;
+                      continue matchEachSubstring;
+                  }
+                  // Could not match current substring
+                  return ConditionResult.FALSE;
+              }
+          }
+
+          if (normFinal != null) {
+              final int finalLength = normFinal.length();
+
+              if (valueLength - finalLength < pos) {
+                  return ConditionResult.FALSE;
+              }
+
+              pos = valueLength - finalLength;
+              for (int i = 0; i < finalLength; i++, pos++) {
+                  if (normFinal.byteAt(i) != normalizedAttributeValue.byteAt(pos)) {
+                      return ConditionResult.FALSE;
+                  }
+              }
+          }
+
+          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) == (byte) 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));
+              }
+          }
+      }
+
+      // TODO : reminder : should add this method in the SDK
+      /** {@inheritDoc} */
+      @Override
+    public int hashCode()
+    {
+      int hashCode = 0;
+      if (normInitial != null)
+      {
+        hashCode += normInitial.hashCode();
+      }
+      if (normAnys != null)
+      {
+        for (ByteString any : normAnys)
+        {
+          hashCode += any.hashCode();
+        }
+      }
+      if (normFinal != null)
+      {
+        hashCode += normFinal.hashCode();
+      }
+      return hashCode;
+    }
+
+      // TODO : reminder : should add this method in the SDK
+      /** {@inheritDoc} */
+      @Override
+      public boolean equals(Object obj)
+      {
+        if (obj == this)
+        {
+          return true;
+        }
+        if (! (obj instanceof DefaultSubstringAssertion))
+        {
+          return false;
+        }
+        DefaultSubstringAssertion other = (DefaultSubstringAssertion) obj;
+        boolean initialCheck = normInitial == null ? other.normInitial == null : normInitial.equals(other.normInitial);
+        if (!initialCheck)
+        {
+          return false;
+        }
+        boolean finalCheck = normFinal == null ? other.normFinal == null : normFinal.equals(other.normFinal);
+        if (!finalCheck)
+        {
+          return false;
+        }
+        boolean anyCheck = normAnys == null ? other.normAnys == null : normAnys.length == other.normAnys.length;
+        if (!anyCheck)
+        {
+          return false;
+        }
+        if (normAnys != null)
+        {
+          for (int i = 0; i < normAnys.length; i++)
+          {
+            if (! normAnys[i].equals(other.normAnys[i]))
+            {
+              return false;
+            }
+          }
+        }
+        return true;
+      }
+
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public Assertion getSubstringAssertion(ByteSequence subInitial,
+      List<? extends ByteSequence> subAnyElements, ByteSequence subFinal)
+      throws DecodeException
+  {
+    final ByteString normInitial = subInitial == null ? null : normalizeSubstring(subInitial);
+
+    ByteString[] normAnys = null;
+    if (subAnyElements != null && !subAnyElements.isEmpty())
+    {
+      normAnys = new ByteString[subAnyElements.size()];
+      for (int i = 0; i < subAnyElements.size(); i++)
+      {
+        normAnys[i] = normalizeSubstring(subAnyElements.get(i));
+      }
+    }
+    final ByteString normFinal = subFinal == null ? null : normalizeSubstring(subFinal);
+
+    return new DefaultSubstringAssertion(normInitial, normAnys, normFinal);
+  }
+
 }
 
diff --git a/opendj3-server-dev/src/server/org/opends/server/api/VirtualAttributeProvider.java b/opendj3-server-dev/src/server/org/opends/server/api/VirtualAttributeProvider.java
index 1a83583..4822fff 100644
--- a/opendj3-server-dev/src/server/org/opends/server/api/VirtualAttributeProvider.java
+++ b/opendj3-server-dev/src/server/org/opends/server/api/VirtualAttributeProvider.java
@@ -26,7 +26,6 @@
  */
 package org.opends.server.api;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
@@ -34,9 +33,9 @@
 import org.forgerock.i18n.slf4j.LocalizedLogger;
 import org.forgerock.opendj.config.server.ConfigException;
 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.opends.server.admin.std.server.VirtualAttributeCfg;
 import org.opends.server.core.SearchOperation;
 import org.opends.server.types.Attribute;
@@ -261,96 +260,28 @@
                                           List<ByteString> subAny,
                                           ByteString subFinal)
   {
-    SubstringMatchingRule matchingRule =
-         rule.getAttributeType().getSubstringMatchingRule();
+    MatchingRule matchingRule = rule.getAttributeType().getSubstringMatchingRule();
     if (matchingRule == null)
     {
       return ConditionResult.UNDEFINED;
     }
 
-
-    ByteString normalizedSubInitial;
-    if (subInitial == null)
+    Assertion assertion;
+    try
     {
-      normalizedSubInitial = null;
+      assertion = matchingRule.getSubstringAssertion(subInitial, subAny, subFinal);
     }
-    else
-    {
-      try
-      {
-        normalizedSubInitial =
-             matchingRule.normalizeSubstring(subInitial);
-      }
-      catch (Exception e)
-      {
-        logger.traceException(e);
-
-        // The substring couldn't be normalized => return "undefined".
-        return ConditionResult.UNDEFINED;
-      }
+    catch(DecodeException e) {
+      logger.traceException(e);
+      return ConditionResult.UNDEFINED;
     }
 
-
-    ArrayList<ByteSequence> normalizedSubAny;
-    if (subAny == null)
-    {
-      normalizedSubAny = null;
-    }
-    else
-    {
-      normalizedSubAny =
-           new ArrayList<ByteSequence>(subAny.size());
-      for (ByteString subAnyElement : subAny)
-      {
-        try
-        {
-          normalizedSubAny.add(matchingRule.normalizeSubstring(
-                                                 subAnyElement));
-        }
-        catch (Exception e)
-        {
-          logger.traceException(e);
-
-          // The substring couldn't be normalized => return "undefined".
-          return ConditionResult.UNDEFINED;
-        }
-      }
-    }
-
-
-    ByteString normalizedSubFinal;
-    if (subFinal == null)
-    {
-      normalizedSubFinal = null;
-    }
-    else
-    {
-      try
-      {
-        normalizedSubFinal =
-             matchingRule.normalizeSubstring(subFinal);
-      }
-      catch (Exception e)
-      {
-        logger.traceException(e);
-
-        // The substring couldn't be normalized => return "undefined".
-        return ConditionResult.UNDEFINED;
-      }
-    }
-
-
     ConditionResult result = ConditionResult.FALSE;
     for (ByteString value : getValues(entry, rule))
     {
       try
       {
-        ByteString nv = matchingRule.normalizeAttributeValue(value);
-        if (matchingRule.valueMatchesSubstring(
-                              nv,
-                              normalizedSubInitial,
-                              normalizedSubAny,
-                              normalizedSubFinal))
+        if (assertion.matches(matchingRule.normalizeAttributeValue(value)).toBoolean())
         {
           return ConditionResult.TRUE;
         }
diff --git a/opendj3-server-dev/src/server/org/opends/server/controls/MatchedValuesFilter.java b/opendj3-server-dev/src/server/org/opends/server/controls/MatchedValuesFilter.java
index 9732d95..318f005 100644
--- a/opendj3-server-dev/src/server/org/opends/server/controls/MatchedValuesFilter.java
+++ b/opendj3-server-dev/src/server/org/opends/server/controls/MatchedValuesFilter.java
@@ -36,7 +36,6 @@
 import org.forgerock.opendj.io.ASN1Reader;
 import org.forgerock.opendj.io.ASN1Writer;
 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.util.Reject;
@@ -117,12 +116,6 @@
   // The approximate matching rule for this matched values filter.
   private MatchingRule approximateMatchingRule;
 
-  // The normalized subFinal value for this matched values filter.
-  private ByteString normalizedSubFinal;
-
-  // The normalized subInitial value for this matched values filter.
-  private ByteString normalizedSubInitial;
-
   // The raw, unprocessed assertion value for this matched values filter.
   private final ByteString rawAssertionValue;
 
@@ -148,9 +141,6 @@
   // The equality matching rule for this matched values filter.
   private MatchingRule equalityMatchingRule;
 
-  // The set of normalized subAny values for this matched values filter.
-  private List<ByteString> normalizedSubAny;
-
   // The set of subAny values for this matched values filter.
   private final List<ByteString> subAny;
 
@@ -169,7 +159,11 @@
   // The substring matching rule for this matched values filter.
   private SubstringMatchingRule substringMatchingRule;
 
-
+  /**
+   * The assertion created from substring matching rule using values of this
+   * filter.
+   */
+  private Assertion substringAssertion;
 
   /**
    * Creates a new matched values filter with the provided information.
@@ -194,18 +188,6 @@
     this.subAny            = subAny;
     this.subFinal          = subFinal;
     this.matchingRuleID    = matchingRuleID;
-
-    decoded                 = false;
-    attributeType           = null;
-    assertionValue          = null;
-    matchingRule            = null;
-    normalizedSubInitial    = null;
-    normalizedSubAny        = null;
-    normalizedSubFinal      = null;
-    approximateMatchingRule = null;
-    equalityMatchingRule    = null;
-    orderingMatchingRule    = null;
-    substringMatchingRule   = null;
   }
 
 
@@ -972,37 +954,20 @@
     return subInitial;
   }
 
-
-
-
-
-
-
-  /**
-   * Retrieves the normalized form of the subInitial element.
-   *
-   * @return  The normalized form of the subInitial element, or
-   *          <CODE>null</CODE> if there is none.
-   */
-  public ByteString getNormalizedSubInitialElement()
-  {
-    if (normalizedSubInitial == null)
+  private Assertion getSubstringAssertion() {
+    if (substringAssertion == null)
     {
-      if ((subInitial != null) && (getSubstringMatchingRule() != null))
+      try
       {
-        try
-        {
-          normalizedSubInitial =
-               getSubstringMatchingRule().normalizeSubstring(subInitial);
-        }
-        catch (Exception e)
-        {
-          logger.traceException(e);
-        }
+        substringAssertion =
+            getSubstringMatchingRule().getSubstringAssertion(subInitial, subAny, subFinal);
+      }
+      catch (DecodeException e)
+      {
+        logger.traceException(e);
       }
     }
-
-    return normalizedSubInitial;
+    return substringAssertion;
   }
 
 
@@ -1019,55 +984,6 @@
     return subAny;
   }
 
-
-
-  /**
-   * Retrieves the set of normalized subAny elements for this matched values
-   * filter.
-   *
-   * @return  The set of subAny elements for this matched values filter.  If
-   *          there are none, then an empty list will be returned.  If a
-   *          problem occurs while attempting to perform the normalization, then
-   *          <CODE>null</CODE> will be returned.
-   */
-  public List<ByteString> getNormalizedSubAnyElements()
-  {
-    if (normalizedSubAny == null)
-    {
-      if ((subAny == null) || (subAny.isEmpty()))
-      {
-        normalizedSubAny = new ArrayList<ByteString>(0);
-      }
-      else
-      {
-        if (getSubstringMatchingRule() == null)
-        {
-          return null;
-        }
-
-        normalizedSubAny = new ArrayList<ByteString>();
-        try
-        {
-          for (ByteString s : subAny)
-          {
-            normalizedSubAny.add(
-                 substringMatchingRule.normalizeSubstring(s));
-          }
-        }
-        catch (Exception e)
-        {
-          logger.traceException(e);
-
-          normalizedSubAny = null;
-        }
-      }
-    }
-
-    return normalizedSubAny;
-  }
-
-
-
   /**
    * Retrieves the subFinal element for this matched values filter.
    *
@@ -1079,37 +995,6 @@
     return subFinal;
   }
 
-
-
-  /**
-   * Retrieves the normalized form of the subFinal element.
-   *
-   * @return  The normalized form of the subFinal element, or <CODE>null</CODE>
-   *          if there is none.
-   */
-  public ByteString getNormalizedSubFinalElement()
-  {
-    if (normalizedSubFinal == null)
-    {
-      if ((subFinal != null) && (getSubstringMatchingRule() != null))
-      {
-        try
-        {
-          normalizedSubFinal =
-               getSubstringMatchingRule().normalizeSubstring(subFinal);
-        }
-        catch (Exception e)
-        {
-          logger.traceException(e);
-        }
-      }
-    }
-
-    return normalizedSubFinal;
-  }
-
-
-
   /**
    * Retrieves the matching rule ID for this matched values filter.
    *
@@ -1247,9 +1132,7 @@
     {
       getAttributeType();
       getAssertionValue();
-      getNormalizedSubInitialElement();
-      getNormalizedSubAnyElements();
-      getNormalizedSubFinalElement();
+      getSubstringAssertion();
       getMatchingRule();
       getApproximateMatchingRule();
       getEqualityMatchingRule();
@@ -1303,15 +1186,11 @@
       case SUBSTRINGS_TYPE:
         if (attributeType != null
             && attributeType.equals(type)
-            && substringMatchingRule != null)
+            && substringAssertion != null)
         {
           try
           {
-            ArrayList<ByteSequence> normalizedSubAnyBS =
-                 new ArrayList<ByteSequence>(normalizedSubAny);
-            return substringMatchingRule.valueMatchesSubstring(
-                 substringMatchingRule.normalizeAttributeValue(value),
-                 normalizedSubInitial, normalizedSubAnyBS, normalizedSubFinal);
+            return substringAssertion.matches(substringMatchingRule.normalizeAttributeValue(value)).toBoolean();
           }
           catch (Exception e)
           {
diff --git a/opendj3-server-dev/src/server/org/opends/server/types/AttributeBuilder.java b/opendj3-server-dev/src/server/org/opends/server/types/AttributeBuilder.java
index 515cf56..a35bd5f 100644
--- a/opendj3-server-dev/src/server/org/opends/server/types/AttributeBuilder.java
+++ b/opendj3-server-dev/src/server/org/opends/server/types/AttributeBuilder.java
@@ -27,7 +27,6 @@
 package org.opends.server.types;
 
 import java.util.AbstractSet;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
@@ -42,7 +41,6 @@
 
 import org.forgerock.i18n.slf4j.LocalizedLogger;
 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;
@@ -381,81 +379,22 @@
         ByteString subInitial,
         List<ByteString> subAny, ByteString subFinal)
     {
-      SubstringMatchingRule matchingRule = attributeType
-          .getSubstringMatchingRule();
+      SubstringMatchingRule matchingRule = attributeType.getSubstringMatchingRule();
       if (matchingRule == null)
       {
         return ConditionResult.UNDEFINED;
       }
 
-      ByteString normalizedSubInitial;
-      if (subInitial == null)
-      {
-        normalizedSubInitial = null;
-      }
-      else
-      {
-        try
-        {
-          normalizedSubInitial =
-            matchingRule.normalizeSubstring(subInitial);
-        }
-        catch (Exception e)
-        {
-          logger.traceException(e);
 
-          // The substring couldn't be normalized. We have to return
-          // "undefined".
-          return ConditionResult.UNDEFINED;
-        }
-      }
-
-      ArrayList<ByteSequence> normalizedSubAny;
-      if (subAny == null)
+      Assertion assertion;
+      try
       {
-        normalizedSubAny = null;
+        assertion = matchingRule.getSubstringAssertion(subInitial, subAny, subFinal);
       }
-      else
+      catch (DecodeException e)
       {
-        normalizedSubAny = new ArrayList<ByteSequence>(subAny.size());
-        for (ByteString subAnyElement : subAny)
-        {
-          try
-          {
-            normalizedSubAny
-                .add(matchingRule.normalizeSubstring(subAnyElement));
-          }
-          catch (Exception e)
-          {
-            logger.traceException(e);
-
-            // The substring couldn't be normalized. We have to return
-            // "undefined".
-            return ConditionResult.UNDEFINED;
-          }
-        }
-      }
-
-      ByteString normalizedSubFinal;
-      if (subFinal == null)
-      {
-        normalizedSubFinal = null;
-      }
-      else
-      {
-        try
-        {
-          normalizedSubFinal =
-            matchingRule.normalizeSubstring(subFinal);
-        }
-        catch (Exception e)
-        {
-          logger.traceException(e);
-
-          // The substring couldn't be normalized. We have to return
-          // "undefined".
-          return ConditionResult.UNDEFINED;
-        }
+        logger.traceException(e);
+        return ConditionResult.UNDEFINED;
       }
 
       ConditionResult result = ConditionResult.FALSE;
@@ -463,11 +402,7 @@
       {
         try
         {
-          if (matchingRule.valueMatchesSubstring(
-              matchingRule.normalizeAttributeValue(value),
-              normalizedSubInitial,
-              normalizedSubAny,
-              normalizedSubFinal))
+          if (assertion.matches(matchingRule.normalizeAttributeValue(value)).toBoolean())
           {
             return ConditionResult.TRUE;
           }
diff --git a/opendj3-server-dev/src/server/org/opends/server/types/SearchFilter.java b/opendj3-server-dev/src/server/org/opends/server/types/SearchFilter.java
index d3a5406..c7c4f52 100644
--- a/opendj3-server-dev/src/server/org/opends/server/types/SearchFilter.java
+++ b/opendj3-server-dev/src/server/org/opends/server/types/SearchFilter.java
@@ -3593,30 +3593,13 @@
     {
       case AND:
       case OR:
-        if (filterComponents.size() != f.filterComponents.size())
-        {
-          return false;
-        }
-
-outerComponentLoop:
-        for (SearchFilter outerFilter : filterComponents)
-        {
-          for (SearchFilter innerFilter : f.filterComponents)
-          {
-            if (outerFilter.equals(innerFilter))
-            {
-              continue outerComponentLoop;
-            }
-          }
-          return false;
-        }
-        return true;
+        return andOrEqual(f);
       case NOT:
         return notComponent.equals(f.notComponent);
       case EQUALITY:
         return typeAndOptionsAndAssertionEqual(f);
       case SUBSTRING:
-        return equalsSubstring(f);
+        return substringEqual(f);
       case GREATER_OR_EQUAL:
         return typeAndOptionsAndAssertionEqual(f);
       case LESS_OR_EQUAL:
@@ -3633,6 +3616,28 @@
     }
   }
 
+  private boolean andOrEqual(SearchFilter f)
+  {
+    if (filterComponents.size() != f.filterComponents.size())
+    {
+      return false;
+    }
+
+outerComponentLoop:
+    for (SearchFilter outerFilter : filterComponents)
+    {
+      for (SearchFilter innerFilter : f.filterComponents)
+      {
+        if (outerFilter.equals(innerFilter))
+        {
+          continue outerComponentLoop;
+        }
+      }
+      return false;
+    }
+    return true;
+  }
+
 
   private boolean typeAndOptionsAndAssertionEqual(SearchFilter f)
   {
@@ -3650,113 +3655,28 @@
   }
 
 
-  private boolean equalsSubstring(SearchFilter f)
+  private boolean substringEqual(SearchFilter f)
   {
     if (! attributeType.equals(f.attributeType))
     {
       return false;
     }
 
-    SubstringMatchingRule smr =
-         attributeType.getSubstringMatchingRule();
-    if (smr == null)
+    SubstringMatchingRule rule = attributeType.getSubstringMatchingRule();
+    if (rule == null)
     {
       return false;
     }
-
-    if (! optionsEqual(attributeOptions, f.attributeOptions))
+    try
+    {
+      Assertion thisAssertion = rule.getSubstringAssertion(subInitialElement, subAnyElements, subFinalElement);
+      Assertion thatAssertion = rule.getSubstringAssertion(f.subInitialElement, f.subAnyElements, f.subFinalElement);
+      return thisAssertion.equals(thatAssertion);
+    }
+    catch (DecodeException e)
     {
       return false;
     }
-
-    if (subInitialElement == null)
-    {
-      if (f.subInitialElement != null)
-      {
-        return false;
-      }
-    }
-    else
-    {
-      if (f.subInitialElement == null)
-      {
-        return false;
-      }
-      try
-      {
-        ByteString nSI1 =
-             smr.normalizeSubstring(subInitialElement);
-        ByteString nSI2 =
-             smr.normalizeSubstring(f.subInitialElement);
-
-        if (! nSI1.equals(nSI2))
-        {
-          return false;
-        }
-      }
-      catch (Exception e)
-      {
-        return false;
-      }
-    }
-
-    if (subFinalElement == null)
-    {
-      if (f.subFinalElement != null)
-      {
-        return false;
-      }
-    }
-    else
-    {
-      if (f.subFinalElement == null)
-      {
-        return false;
-      }
-      try
-      {
-        ByteString nSF1 =
-             smr.normalizeSubstring(subFinalElement);
-        ByteString nSF2 =
-             smr.normalizeSubstring(f.subFinalElement);
-
-        if (! nSF1.equals(nSF2))
-        {
-          return false;
-        }
-      }
-      catch (Exception e)
-      {
-        return false;
-      }
-    }
-
-    if (subAnyElements.size() != f.subAnyElements.size())
-    {
-      return false;
-    }
-
-    for (int i = 0; i < subAnyElements.size(); i++)
-    {
-      try
-      {
-        ByteString nSA1 =
-             smr.normalizeSubstring(subAnyElements.get(i));
-        ByteString nSA2 =
-             smr.normalizeSubstring(f.subAnyElements.get(i));
-
-        if (! nSA1.equals(nSA2))
-        {
-          return false;
-        }
-      }
-      catch (Exception e)
-      {
-        return false;
-      }
-    }
-
-    return true;
   }
 
 
@@ -3820,10 +3740,8 @@
       }
       else
       {
-        MatchingRule mr =
-             DirectoryServer.getMatchingRule(
-                  toLowerCase(matchingRuleID));
-        if (mr == null)
+        MatchingRule mrule = DirectoryServer.getMatchingRule(toLowerCase(matchingRuleID));
+        if (mrule == null)
         {
           return false;
         }
@@ -3831,8 +3749,8 @@
         {
           try
           {
-            Assertion assertion = mr.getAssertion(f.assertionValue);
-            return assertion.matches(mr.normalizeAttributeValue(assertionValue)).toBoolean();
+            Assertion assertion = mrule.getAssertion(f.assertionValue);
+            return assertion.matches(mrule.normalizeAttributeValue(assertionValue)).toBoolean();
           }
           catch (Exception e)
           {
@@ -3921,24 +3839,7 @@
       case EQUALITY:
         return typeAndAssertionHashCode();
       case SUBSTRING:
-        hashCode = attributeType.hashCode();
-
-        SubstringMatchingRule smr =
-             attributeType.getSubstringMatchingRule();
-
-        hashCode = hashCode(hashCode, smr, subInitialElement);
-
-        if (subAnyElements != null)
-        {
-          for (ByteString e : subAnyElements)
-          {
-              hashCode = hashCode(hashCode, smr, e);
-          }
-        }
-
-        hashCode = hashCode(hashCode, smr, subFinalElement);
-
-        return hashCode;
+        return substringHashCode();
       case GREATER_OR_EQUAL:
         return typeAndAssertionHashCode();
       case LESS_OR_EQUAL:
@@ -3948,35 +3849,41 @@
       case APPROXIMATE_MATCH:
         return typeAndAssertionHashCode();
       case EXTENSIBLE_MATCH:
-        hashCode = 0;
-
-        if (attributeType != null)
-        {
-          hashCode += attributeType.hashCode();
-        }
-
-        if (dnAttributes)
-        {
-          hashCode++;
-        }
-
-        if (matchingRuleID != null)
-        {
-          hashCode += matchingRuleID.hashCode();
-        }
-
-        if (assertionValue != null)
-        {
-          hashCode += assertionValue.hashCode();
-        }
-
-        return hashCode;
+        return extensibleHashCode();
       default:
         return 1;
     }
   }
 
 
+  /** Returns the hash code for extensible filter. */
+  private int extensibleHashCode()
+  {
+    int hashCode = 0;
+
+    if (attributeType != null)
+    {
+      hashCode += attributeType.hashCode();
+    }
+
+    if (dnAttributes)
+    {
+      hashCode++;
+    }
+
+    if (matchingRuleID != null)
+    {
+      hashCode += matchingRuleID.hashCode();
+    }
+
+    if (assertionValue != null)
+    {
+      hashCode += assertionValue.hashCode();
+    }
+    return hashCode;
+  }
+
+
   private int typeAndAssertionHashCode()
   {
     final int hashCode = attributeType.hashCode();
@@ -3990,25 +3897,30 @@
     }
   }
 
-
-  private int hashCode(int hashCode, SubstringMatchingRule smr,
-      ByteString subElem)
+  /** Returns hash code to use for substring filter. */
+  private int substringHashCode()
   {
-    if (subElem != null)
+    int hashCode = attributeType.hashCode();
+    final MatchingRule rule = attributeType.getSubstringMatchingRule();
+    if (rule != null)
     {
-      if (smr == null)
+      try
       {
-        hashCode += subElem.hashCode();
+        return hashCode + rule.getSubstringAssertion(subInitialElement, subAnyElements, subFinalElement).hashCode();
       }
-      else
+      catch (DecodeException e)
       {
-        try
-        {
-          hashCode += smr.normalizeSubstring(subElem).hashCode();
-        }
-        catch (Exception e) {}
+        logger.traceException(e);
       }
     }
+
+    // Fallback to hash code based on elements
+    hashCode += subInitialElement.hashCode();
+    for (ByteString e : subAnyElements)
+    {
+      hashCode += e.hashCode();
+    }
+    hashCode += subFinalElement.hashCode();
     return hashCode;
   }
 
diff --git a/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/CaseExactIA5SubstringMatchingRuleTest.java b/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/CaseExactIA5SubstringMatchingRuleTest.java
index a3cd75e..c3b8577 100644
--- a/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/CaseExactIA5SubstringMatchingRuleTest.java
+++ b/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/CaseExactIA5SubstringMatchingRuleTest.java
@@ -22,6 +22,7 @@
  *
  *
  *      Copyright 2006-2008 Sun Microsystems, Inc.
+ *      Portions Copyright 2014 ForgeRock AS
  */
 package org.opends.server.schema;
 
@@ -35,6 +36,38 @@
     SubstringMatchingRuleTest
 {
 
+  /** {@inheritDoc} */
+  @Override
+  @DataProvider(name="substringMatchData")
+  public Object[][] createSubstringMatchData()
+  {
+    return new Object[][] {
+        {"this is a value", "", new String[] {"this"}, "", true },
+        {"this is a value", "", new String[] {"is"}, "", true },
+        {"this is a value", "th", new String[] {"is"}, "", true },
+        {"this is a value", "this", new String[] {"is"}, "", true },
+        {"this is a value", "this", new String[] {"is"}, "value", true },
+        {"this is a value", "this", new String[] {"is"}, "ue", true },
+        {"this is a value", "this", new String[] {"is"}, "e", true },
+        {"this is a value", "this", new String[] {"is"}, "valu", false },
+        {"this is a value", "THIS", new String[] {"is"}, "", false },
+        {"this is a value", "h", new String[] {"is"}, "", false },
+        {"this is a value", "", new String[] {"a"}, "", true },
+        {"this is a value", "", new String[] {"value"}, "", true },
+        {"this is a value", "", new String[] {" "}, "", true },
+        {"this is a value", "", new String[] {"this", "is", "a", "value"}, "", true },
+         // The matching rule requires ordered non overlapping substrings
+         // Issue #730 was not valid.
+        {"this is a value", "", new String[] {"value", "this"}, "", false },
+        {"this is a value", "", new String[] {"this", "this is"}, "", false },
+        {"this is a value", "", new String[] {"his is", "a val",}, "", true },
+        {"this is a value", "", new String[] {"not",}, "", false },
+        {"this is a value", "", new String[] {"THIS",}, "", false },
+        {"this is a value", "", new String[] {"this", "not"}, "", false },
+        {"this is a value", "", new String[] {"    "}, "", true },
+    };
+  }
+
   /**
    * {@inheritDoc}
    */
diff --git a/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/CaseExactSubstringMatchingRuleTest.java b/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/CaseExactSubstringMatchingRuleTest.java
index cc1bc60..f09c098 100644
--- a/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/CaseExactSubstringMatchingRuleTest.java
+++ b/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/CaseExactSubstringMatchingRuleTest.java
@@ -22,6 +22,7 @@
  *
  *
  *      Copyright 2006-2008 Sun Microsystems, Inc.
+ *      Portions Copyright 2014 ForgeRock AS
  */
 package org.opends.server.schema;
 
@@ -35,6 +36,38 @@
     SubstringMatchingRuleTest
 {
 
+  /** {@inheritDoc} */
+  @Override
+  @DataProvider(name="substringMatchData")
+  public Object[][] createSubstringMatchData()
+  {
+    return new Object[][] {
+        {"this is a value", "", new String[] {"this"}, "", true },
+        {"this is a value", "", new String[] {"is"}, "", true },
+        {"this is a value", "th", new String[] {"is"}, "", true },
+        {"this is a value", "this", new String[] {"is"}, "", true },
+        {"this is a value", "this", new String[] {"is"}, "value", true },
+        {"this is a value", "this", new String[] {"is"}, "ue", true },
+        {"this is a value", "this", new String[] {"is"}, "e", true },
+        {"this is a value", "this", new String[] {"is"}, "valu", false },
+        {"this is a value", "THIS", new String[] {"is"}, "", false },
+        {"this is a value", "h", new String[] {"is"}, "", false },
+        {"this is a value", "", new String[] {"a"}, "", true },
+        {"this is a value", "", new String[] {"value"}, "", true },
+        {"this is a value", "", new String[] {" "}, "", true },
+        {"this is a value", "", new String[] {"this", "is", "a", "value"}, "", true },
+         // The matching rule requires ordered non overlapping substrings
+         // Issue #730 was not valid.
+        {"this is a value", "", new String[] {"value", "this"}, "", false },
+        {"this is a value", "", new String[] {"this", "this is"}, "", false },
+        {"this is a value", "", new String[] {"his is", "a val",}, "", true },
+        {"this is a value", "", new String[] {"not",}, "", false },
+        {"this is a value", "", new String[] {"THIS",}, "", false },
+        {"this is a value", "", new String[] {"this", "not"}, "", false },
+        {"this is a value", "", new String[] {"    "}, "", true },
+    };
+  }
+
   /**
    * {@inheritDoc}
    */
diff --git a/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/CaseIgnoreIA5SubstringMatchingRuleTest.java b/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/CaseIgnoreIA5SubstringMatchingRuleTest.java
index 2681108..8c57b96 100644
--- a/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/CaseIgnoreIA5SubstringMatchingRuleTest.java
+++ b/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/CaseIgnoreIA5SubstringMatchingRuleTest.java
@@ -22,6 +22,7 @@
  *
  *
  *      Copyright 2006-2008 Sun Microsystems, Inc.
+ *      Portions Copyright 2014 ForgeRock AS
  */
 package org.opends.server.schema;
 
@@ -35,6 +36,38 @@
     SubstringMatchingRuleTest
 {
 
+  /** {@inheritDoc} */
+  @Override
+  @DataProvider(name="substringMatchData")
+  public Object[][] createSubstringMatchData()
+  {
+    return new Object[][] {
+        {"this is a value", "", new String[] {"this"}, "", true },
+        {"this is a value", "", new String[] {"is"}, "", true },
+        {"this is a value", "th", new String[] {"is"}, "", true },
+        {"this is a value", "this", new String[] {"is"}, "", true },
+        {"this is a value", "this", new String[] {"is"}, "value", true },
+        {"this is a value", "this", new String[] {"is"}, "ue", true },
+        {"this is a value", "this", new String[] {"is"}, "e", true },
+        {"this is a value", "this", new String[] {"is"}, "valu", false },
+        {"this is a value", "THIS", new String[] {"is"}, "", true },
+        {"this is a value", "h", new String[] {"is"}, "", false },
+        {"this is a value", "", new String[] {"a"}, "", true },
+        {"this is a value", "", new String[] {"value"}, "", true },
+        {"this is a value", "", new String[] {" "}, "", true },
+        {"this is a value", "", new String[] {"this", "is", "a", "value"}, "", true },
+         // The matching rule requires ordered non overlapping substrings
+         // Issue #730 was not valid.
+        {"this is a value", "", new String[] {"value", "this"}, "", false },
+        {"this is a value", "", new String[] {"this", "this is"}, "", false },
+        {"this is a value", "", new String[] {"his is", "a val",}, "", true },
+        {"this is a value", "", new String[] {"not",}, "", false },
+        {"this is a value", "", new String[] {"THIS",}, "", true },
+        {"this is a value", "", new String[] {"this", "not"}, "", false },
+        {"this is a value", "", new String[] {"    "}, "", true },
+    };
+  }
+
   /**
    * {@inheritDoc}
    */
diff --git a/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/CaseIgnoreSubstringMatchingRuleTest.java b/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/CaseIgnoreSubstringMatchingRuleTest.java
index 07eaabe..e3dabde 100644
--- a/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/CaseIgnoreSubstringMatchingRuleTest.java
+++ b/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/CaseIgnoreSubstringMatchingRuleTest.java
@@ -22,6 +22,7 @@
  *
  *
  *      Copyright 2006-2008 Sun Microsystems, Inc.
+ *      Portions Copyright 2014 ForgeRock AS
  */
 package org.opends.server.schema;
 
@@ -35,6 +36,38 @@
     SubstringMatchingRuleTest
 {
 
+  /** {@inheritDoc} */
+  @Override
+  @DataProvider(name="substringMatchData")
+  public Object[][] createSubstringMatchData()
+  {
+    return new Object[][] {
+        {"this is a value", "", new String[] {"this"}, "", true },
+        {"this is a value", "", new String[] {"is"}, "", true },
+        {"this is a value", "th", new String[] {"is"}, "", true },
+        {"this is a value", "this", new String[] {"is"}, "", true },
+        {"this is a value", "this", new String[] {"is"}, "value", true },
+        {"this is a value", "this", new String[] {"is"}, "ue", true },
+        {"this is a value", "this", new String[] {"is"}, "e", true },
+        {"this is a value", "this", new String[] {"is"}, "valu", false },
+        {"this is a value", "THIS", new String[] {"is"}, "", true },
+        {"this is a value", "h", new String[] {"is"}, "", false },
+        {"this is a value", "", new String[] {"a"}, "", true },
+        {"this is a value", "", new String[] {"value"}, "", true },
+        {"this is a value", "", new String[] {" "}, "", true },
+        {"this is a value", "", new String[] {"this", "is", "a", "value"}, "", true },
+         // The matching rule requires ordered non overlapping substrings
+         // Issue #730 was not valid.
+        {"this is a value", "", new String[] {"value", "this"}, "", false },
+        {"this is a value", "", new String[] {"this", "this is"}, "", false },
+        {"this is a value", "", new String[] {"his is", "a val",}, "", true },
+        {"this is a value", "", new String[] {"not",}, "", false },
+        {"this is a value", "", new String[] {"THIS",}, "", true },
+        {"this is a value", "", new String[] {"this", "not"}, "", false },
+        {"this is a value", "", new String[] {"    "}, "", true },
+    };
+  }
+
   /**
    * {@inheritDoc}
    */
diff --git a/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/NumericStringSubstringMatchingRuleTest.java b/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/NumericStringSubstringMatchingRuleTest.java
index 90f9c53..2bf0243 100644
--- a/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/NumericStringSubstringMatchingRuleTest.java
+++ b/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/NumericStringSubstringMatchingRuleTest.java
@@ -22,6 +22,7 @@
  *
  *
  *      Copyright 2006-2008 Sun Microsystems, Inc.
+ *      Portions Copyright 2014 ForgeRock AS
  */
 package org.opends.server.schema;
 
@@ -35,6 +36,30 @@
     SubstringMatchingRuleTest
 {
 
+  /** {@inheritDoc} */
+  @Override
+  @DataProvider(name="substringMatchData")
+  public Object[][] createSubstringMatchData()
+  {
+    return new Object[][] {
+      // The matching rule requires ordered non overlapping substrings.
+      // Issue #730 was not valid.
+     {"123456789", "", new String[] {"123", "234", "567", "789"}, "",  false },
+     {"123456789", "", new String[] {"123", "234"}, "",  false },
+     {"123456789", "", new String[] {"567", "234"}, "",  false },
+     {"123456789", "", new String[] {"123", "456"}, "",  true },
+     {"123456789", "", new String[] {"123"}, "",  true },
+     {"123456789", "", new String[] {"456"}, "",  true },
+     {"123456789", "", new String[] {"789"}, "",  true },
+     {"123456789", "", new String[] {"123456789"}, "",  true },
+     {"123456789", "", new String[] {"1234567890"}, "",  false },
+     {"123456789", "", new String[] {"9"}, "",  true },
+     {"123456789", "", new String[] {"1"}, "",  true },
+     {"123456789", "", new String[] {"0"}, "",  false },
+     {"123456789", "", new String[] {"    "}, "",  true },
+     {"123456789", "", new String[] {"0123"}, "",  false },
+    };
+  }
   /**
    * {@inheritDoc}
    */
diff --git a/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/SubstringMatchingRuleTest.java b/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/SubstringMatchingRuleTest.java
index 8c28ce1..106255c 100644
--- a/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/SubstringMatchingRuleTest.java
+++ b/opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/SubstringMatchingRuleTest.java
@@ -30,6 +30,7 @@
 import java.util.List;
 
 import org.opends.server.api.SubstringMatchingRule;
+import org.forgerock.opendj.ldap.Assertion;
 import org.forgerock.opendj.ldap.ByteString;
 import org.forgerock.opendj.ldap.ByteSequence;
 import org.testng.annotations.DataProvider;
@@ -42,9 +43,16 @@
  * This class is intended to be extended by one class for each substring
  * matching rules.
  */
+@SuppressWarnings("javadoc")
 public abstract class SubstringMatchingRuleTest extends SchemaTestCase
 {
   /**
+   * Generate data for the test of the assertion match.
+   */
+  @DataProvider(name="substringMatchData")
+  public abstract Object[][] createSubstringMatchData();
+
+  /**
    * Generate data for the test of the middle string match.
    *
    * @return the data for the test of the middle string match.
@@ -114,8 +122,7 @@
    * Test the normalization and the initial substring match.
    */
   @Test(dataProvider= "substringInitialMatchData")
-  public void initialMatchingRules(
-      String value, String initial, Boolean result) throws Exception
+  public void initialMatchingRules(String value, String initial, Boolean result) throws Exception
   {
     SubstringMatchingRule rule = getRule();
 
@@ -140,8 +147,7 @@
    * Test the normalization and the final substring match.
    */
   @Test(dataProvider= "substringFinalMatchData")
-  public void finalMatchingRules(
-      String value, String finalValue, Boolean result) throws Exception
+  public void finalMatchingRules(String value, String finalValue, Boolean result) throws Exception
   {
     SubstringMatchingRule rule = getRule();
 
@@ -160,4 +166,25 @@
           value + " and " + finalValue);
     }
   }
+
+  @Test(dataProvider= "substringMatchData")
+  public void testSubstringAssertion(String value, String initialSub, String[] middleSubs, String finalSub,
+      Boolean expectedResult) throws Exception
+  {
+    SubstringMatchingRule rule = getRule();
+    ByteString normalizedValue = rule.normalizeAttributeValue(ByteString.valueOf(value));
+    ArrayList<ByteSequence> anySubs = new ArrayList<ByteSequence>(middleSubs.length);
+    for (String sub : middleSubs)
+    {
+      anySubs.add(ByteString.valueOf(sub));
+    }
+    Assertion assertion = rule.getSubstringAssertion(
+        ByteString.valueOf(initialSub),
+        anySubs,
+        ByteString.valueOf(finalSub));
+
+    Boolean result = assertion.matches(normalizedValue).toBoolean();
+    assertEquals(result,  expectedResult);
+
+  }
 }

--
Gitblit v1.10.0