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

Nicolas Capponi
24.45.2014 3dc3f07607a5c720a1452cc75701554a9f3ad715
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);
  }
}