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

Jean-Noel Rouvignac
14.04.2014 f4d8494facec3b6eb56487c7585df56e96944c78
OPENDJ-1308 Migrate schema support


Added many tests for TimeBasedMatchingRuleFactory in view of porting this class to the SDK.
Started to adapt its API to the SDK APIs.



NotImplementedAssertion.java: ADDED
Added to stop adding the same code for the not implemented Assertion.createIndexQuery().
To be removed once we switch the schema to the SDK.

AbstractMatchingRule.java
Used NotImplementedAssertion.

AbstractOrderingMatchingRule.java:
Implemented getAssertion().

CollationMatchingRuleFactory.java:
Extracted method copyNames().

TimeBasedMatchingRuleFactory.java:
Extracted multiplyByTenThenAddUnits().
Implemented {RelativeTimeGTOrderingMatchingRule|RelativeTimeLTOrderingMatchingRule}.getAssertion() + moved createIndexQuery() to this inner class.
In normalizeAssertionValue(), made code more readable.
Extracted method toCalendarMonth(), isDateInvalid(), isLeapYear(), logAndThrow(), createExactMatchQuery().
Fixed javadocs.


TimeBasedMatchingRuleFactoryTest.java: ADDED

TimeBasedMatchingRuleTest.java:
Code cleanup.
2 files added
5 files modified
1103 ■■■■■ changed files
opendj3-server-dev/src/server/org/opends/server/api/AbstractMatchingRule.java 11 ●●●●● patch | view | raw | blame | history
opendj3-server-dev/src/server/org/opends/server/api/NotImplementedAssertion.java 57 ●●●●● patch | view | raw | blame | history
opendj3-server-dev/src/server/org/opends/server/schema/AbstractOrderingMatchingRule.java 19 ●●●●● patch | view | raw | blame | history
opendj3-server-dev/src/server/org/opends/server/schema/CollationMatchingRuleFactory.java 116 ●●●● patch | view | raw | blame | history
opendj3-server-dev/src/server/org/opends/server/schema/TimeBasedMatchingRuleFactory.java 627 ●●●●● patch | view | raw | blame | history
opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/TimeBasedMatchingRuleFactoryTest.java 234 ●●●●● patch | view | raw | blame | history
opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/TimeBasedMatchingRuleTest.java 39 ●●●● patch | view | raw | blame | history
opendj3-server-dev/src/server/org/opends/server/api/AbstractMatchingRule.java
@@ -35,7 +35,6 @@
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.opendj.ldap.schema.Syntax;
import org.forgerock.opendj.ldap.spi.IndexQueryFactory;
/**
 * This class provides default implementation of MatchingRule. A
@@ -99,21 +98,13 @@
      throws DecodeException
  {
    final ByteString assertionValue = normalizeAssertionValue(value);
    return new Assertion()
    return new NotImplementedAssertion()
    {
      @Override
      public ConditionResult matches(ByteSequence attributeValue)
      {
        return valuesMatch(attributeValue, assertionValue);
      }
      @Override
      public <T> T createIndexQuery(IndexQueryFactory<T> factory)
          throws DecodeException
      {
        throw new RuntimeException("Not implemented");
      }
    };
  }
opendj3-server-dev/src/server/org/opends/server/api/NotImplementedAssertion.java
New file
@@ -0,0 +1,57 @@
/*
 * 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.opends.server.api;
import org.forgerock.opendj.ldap.Assertion;
import org.forgerock.opendj.ldap.ByteSequence;
import org.forgerock.opendj.ldap.ConditionResult;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.spi.IndexQueryFactory;
/**
 * Avoids repeating again and again the same code when
 * Assertion.createIndexQuery() is not implemented.
 * <p>
 * To be removed once we switch the schema to the SDK.
 */
public class NotImplementedAssertion implements Assertion
{
  /** {@inheritDoc} */
  @Override
  public ConditionResult matches(ByteSequence normalizedAttributeValue)
  {
    throw new RuntimeException("Not implemented");
  }
  /** {@inheritDoc} */
  @Override
  public <T> T createIndexQuery(IndexQueryFactory<T> factory)
      throws DecodeException
  {
    throw new RuntimeException("Not implemented");
  }
}
opendj3-server-dev/src/server/org/opends/server/schema/AbstractOrderingMatchingRule.java
@@ -24,7 +24,9 @@
 */
package org.opends.server.schema;
import org.forgerock.opendj.ldap.*;
import org.opends.server.api.AbstractMatchingRule;
import org.opends.server.api.NotImplementedAssertion;
import org.opends.server.api.OrderingMatchingRule;
/**
@@ -50,4 +52,21 @@
    return null;
  }
  /** {@inheritDoc} */
  @Override
  public Assertion getAssertion(final ByteSequence value)
      throws DecodeException
  {
    final ByteString assertionValue = normalizeAssertionValue(value);
    return new NotImplementedAssertion()
    {
      @Override
      public ConditionResult matches(ByteSequence attributeValue)
      {
        return ConditionResult.valueOf(
            compareValues(assertionValue, attributeValue) < 0);
      }
    };
  }
}
opendj3-server-dev/src/server/org/opends/server/schema/CollationMatchingRuleFactory.java
@@ -33,6 +33,7 @@
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.config.server.ConfigException;
import org.forgerock.opendj.ldap.*;
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.opendj.ldap.spi.IndexQueryFactory;
@@ -41,7 +42,6 @@
import org.opends.server.admin.std.meta.CollationMatchingRuleCfgDefn.MatchingRuleType;
import org.opends.server.admin.std.server.CollationMatchingRuleCfg;
import org.opends.server.api.*;
import org.forgerock.opendj.config.server.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DirectoryException;
@@ -369,8 +369,7 @@
    // If it comes here we don't need to verify MatchingRuleType; it
    // should be okay as its syntax is verified by the admin framework.
    // Iterate over the collations and verify if the format is okay.
    // Also,
    // verify if the locale is allowed by the JVM.
    // Also, verify if the locale is allowed by the JVM.
    for (String collation : configuration.getCollation())
    {
      CollationMapper mapper = new CollationMapper(collation);
@@ -404,6 +403,18 @@
  private Collection<String> copyNames(MatchingRule matchingRule)
  {
    Collection<String> defaultNames = new HashSet<String>();
    if (matchingRule != null)
    {
      defaultNames.addAll(matchingRule.getNames());
    }
    return defaultNames;
  }
  /**
   * Creates Less-than Matching Rule.
   *
@@ -420,20 +431,11 @@
    String oid = mapper.getNumericOID() + ".1";
    String lTag = mapper.getLanguageTag();
    Collection<String> names = new HashSet<String>();
    MatchingRule matchingRule = getMatchingRule(oid);
    if (matchingRule != null)
    {
      for (String name : matchingRule.getNames())
      {
        names.add(name);
      }
    }
    Collection<String> names = copyNames(getMatchingRule(oid));
    names.add(lTag + ".lt");
    names.add(lTag + ".1");
    matchingRule =
    MatchingRule matchingRule =
        new CollationLessThanMatchingRule(oid, names, locale);
    addMatchingRule(oid, matchingRule);
  }
@@ -456,20 +458,11 @@
    String oid = mapper.getNumericOID() + ".2";
    String lTag = mapper.getLanguageTag();
    Collection<String> names = new HashSet<String>();
    MatchingRule matchingRule = getMatchingRule(oid);
    if (matchingRule != null)
    {
      for (String name : matchingRule.getNames())
      {
        names.add(name);
      }
    }
    Collection<String> names = copyNames(getMatchingRule(oid));
    names.add(lTag + ".lte");
    names.add(lTag + ".2");
    matchingRule =
    MatchingRule matchingRule =
        new CollationLessThanOrEqualToMatchingRule(oid, names, locale);
    addMatchingRule(oid, matchingRule);
  }
@@ -496,38 +489,19 @@
    String lTag = mapper.getLanguageTag();
    String nOID = mapper.getNumericOID();
    MatchingRule matchingRule = getMatchingRule(nOID);
    Collection<String> defaultNames = new HashSet<String>();
    if (matchingRule != null)
    {
      for (String name : matchingRule.getNames())
      {
        defaultNames.add(name);
      }
    }
    Collection<String> defaultNames = copyNames(getMatchingRule(nOID));
    defaultNames.add(lTag);
    matchingRule =
        new CollationEqualityMatchingRule(nOID,
                                      defaultNames, locale);
    MatchingRule matchingRule =
        new CollationEqualityMatchingRule(nOID, defaultNames, locale);
    addMatchingRule(nOID, matchingRule);
    Collection<String> names = new HashSet<String>();
    // Register OID.3 as the equality matching rule.
    String OID = mapper.getNumericOID() + ".3";
    MatchingRule equalityMatchingRule = getMatchingRule(OID);
    if (equalityMatchingRule != null)
    {
      for (String name : equalityMatchingRule.getNames())
      {
        names.add(name);
      }
    }
    Collection<String> names = copyNames(getMatchingRule(OID));
    names.add(lTag + ".eq");
    names.add(lTag + ".3");
    equalityMatchingRule =
    MatchingRule equalityMatchingRule =
        new CollationEqualityMatchingRule(OID, names, locale);
    addMatchingRule(OID, equalityMatchingRule);
  }
@@ -550,21 +524,12 @@
    String oid = mapper.getNumericOID() + ".4";
    String lTag = mapper.getLanguageTag();
    Collection<String> names = new HashSet<String>();
    MatchingRule matchingRule = getMatchingRule(oid);
    if (matchingRule != null)
    {
      for (String name : matchingRule.getNames())
      {
        names.add(name);
      }
    }
    Collection<String> names = copyNames(getMatchingRule(oid));
    names.add(lTag + ".gte");
    names.add(lTag + ".4");
    matchingRule =
        new CollationGreaterThanOrEqualToMatchingRule(oid, names,
            locale);
    MatchingRule matchingRule =
        new CollationGreaterThanOrEqualToMatchingRule(oid, names, locale);
    addMatchingRule(oid, matchingRule);
  }
@@ -586,19 +551,11 @@
    String oid = mapper.getNumericOID() + ".5";
    String lTag = mapper.getLanguageTag();
    Collection<String> names = new HashSet<String>();
    MatchingRule matchingRule = getMatchingRule(oid);
    if (matchingRule != null)
    {
      for (String name : matchingRule.getNames())
      {
        names.add(name);
      }
    }
    Collection<String> names = copyNames(getMatchingRule(oid));
    names.add(lTag + ".gt");
    names.add(lTag + ".5");
    matchingRule =
    MatchingRule matchingRule =
        new CollationGreaterThanMatchingRule(oid, names, locale);
    addMatchingRule(oid, matchingRule);
  }
@@ -621,18 +578,11 @@
    String oid = mapper.getNumericOID() + ".6";
    String lTag = mapper.getLanguageTag();
    Collection<String> names = new HashSet<String>();
    MatchingRule matchingRule = getMatchingRule(oid);
    if (matchingRule != null)
    {
      for (String name : matchingRule.getNames())
      {
        names.add(name);
      }
    }
    Collection<String> names = copyNames(getMatchingRule(oid));
    names.add(lTag + ".sub");
    names.add(lTag + ".6");
    matchingRule =
    MatchingRule matchingRule =
        new CollationSubstringMatchingRule(oid, names, locale);
    addMatchingRule(oid, matchingRule);
  }
opendj3-server-dev/src/server/org/opends/server/schema/TimeBasedMatchingRuleFactory.java
@@ -24,8 +24,6 @@
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Portions Copyright 2011-2014 ForgeRock AS
 */
package org.opends.server.schema;
import java.nio.ByteBuffer;
@@ -33,6 +31,8 @@
import org.forgerock.i18n.LocalizableMessage;
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.ByteStringBuilder;
@@ -43,7 +43,6 @@
import org.forgerock.opendj.ldap.spi.IndexingOptions;
import org.opends.server.admin.std.server.MatchingRuleCfg;
import org.opends.server.api.*;
import org.forgerock.opendj.config.server.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.InitializationException;
@@ -56,8 +55,6 @@
import static org.opends.server.util.StaticUtils.*;
import static org.opends.server.util.TimeThread.*;
/**
 * This class acts as a factory for time-based matching rules.
 */
@@ -67,27 +64,23 @@
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
  //Greater-than RelativeTimeMatchingRule.
  /** Greater-than RelativeTimeMatchingRule. */
  private MatchingRule greaterThanRTMRule;
  //Less-than RelativeTimeMatchingRule.
  /** Less-than RelativeTimeMatchingRule. */
  private MatchingRule lessThanRTMRule;
  //PartialDayAndTimeMatchingRule.
  /** PartialDayAndTimeMatchingRule. */
  private MatchingRule partialDTMatchingRule;
  //A Collection of matching rules managed by this factory.
  /** A Collection of matching rules managed by this factory. */
  private Set<MatchingRule> matchingRules;
  private static final TimeZone TIME_ZONE_UTC_OBJ =
      TimeZone.getTimeZone(TIME_ZONE_UTC);
  //Constants for generating keys.
  /** Constants for generating keys. */
  private static final char SECOND = 's';
  private static final char MINUTE = 'm';
  private static final char HOUR = 'h';
@@ -96,9 +89,7 @@
  private static final char YEAR = 'Y';
  /**
   * {@inheritDoc}
   */
  /** {@inheritDoc} */
  @Override
  public void initializeMatchingRule(MatchingRuleCfg configuration)
          throws ConfigException, InitializationException
@@ -114,9 +105,7 @@
  /**
   * {@inheritDoc}
   */
  /** {@inheritDoc} */
  @Override
  public Collection<MatchingRule> getMatchingRules()
  {
@@ -131,9 +120,7 @@
  private  abstract class TimeBasedMatchingRule extends AbstractMatchingRule
          implements ExtensibleMatchingRule
  {
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public String getDescription()
    {
@@ -143,9 +130,7 @@
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public String getSyntaxOID()
    {
@@ -154,9 +139,7 @@
    /**
      * {@inheritDoc}
      */
    /** {@inheritDoc} */
    @Override
    public ByteString normalizeAttributeValue(ByteSequence value)
            throws DecodeException
@@ -198,19 +181,17 @@
     * This value was generated using the <CODE>serialver</CODE> command-line
     * utility included with the Java SDK.
     */
     private static final long serialVersionUID = -3501812894473163490L;
    private static final long serialVersionUID = -3501812894473163490L;
     /**
      * Indexer associated with this instance.
      */
     protected ExtensibleIndexer indexer;
    /**
     * Indexer associated with this instance.
     */
    protected ExtensibleIndexer indexer;
     /**
      * {@inheritDoc}
      */
    /** {@inheritDoc} */
    @Override
    public ByteString normalizeAssertionValue(ByteSequence value)
        throws DecodeException
@@ -257,55 +238,14 @@
      long week = 0;
      boolean containsTimeUnit = false;
      long number = 0;
      int number = 0;
      for(; index<value.length(); index++)
      {
        byte b = value.byteAt(index);
        if(isDigit((char)b))
        {
          switch (value.byteAt(index))
          {
            case '0':
              number = (number * 10);
              break;
            case '1':
              number = (number * 10) + 1;
              break;
            case '2':
              number = (number * 10) + 2;
              break;
            case '3':
              number = (number * 10) + 3;
              break;
            case '4':
              number = (number * 10) + 4;
              break;
            case '5':
              number = (number * 10) + 5;
              break;
            case '6':
              number = (number * 10) + 6;
              break;
            case '7':
              number = (number * 10) + 7;
              break;
            case '8':
              number = (number * 10) + 8;
              break;
            case '9':
              number = (number * 10) + 9;
              break;
          }
          number = multiplyByTenThenAddUnits(number, b);
        }
        else
        {
@@ -317,7 +257,7 @@
          }
          else
          {
            switch(value.byteAt(index))
            switch(b)
            {
              case 's':
                second = number;
@@ -335,8 +275,7 @@
                week = number;
                break;
              default:
                  message = WARN_ATTR_INVALID_RELATIVE_TIME_ASSERTION_FORMAT.
                          get(value,(char)value.byteAt(index));
                message = WARN_ATTR_INVALID_RELATIVE_TIME_ASSERTION_FORMAT.get(value, (char) b);
            }
          }
          if(message !=null)
@@ -345,11 +284,8 @@
            logger.error(message);
            throw DecodeException.error(message);
          }
          else
          {
            containsTimeUnit = true;
            number = 0;
          }
          containsTimeUnit = true;
          number = 0;
        }
      }
@@ -362,15 +298,12 @@
      long delta = (second + minute*60 +  hour*3600 + day*24*3600 +
              week*7*24*3600)*1000 ;
      long now = getTime();
      return signed?ByteString.valueOf(now-delta):
                            ByteString.valueOf(now+delta);
      return ByteString.valueOf(signed ? now - delta : now + delta);
    }
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public int compareValues(ByteSequence value1, ByteSequence value2)
    {
@@ -379,9 +312,7 @@
    /**
      * {@inheritDoc}
      */
    /** {@inheritDoc} */
    @Override
    public int compare(byte[] arg0, byte[] arg1)
    {
@@ -390,9 +321,7 @@
    /**
    * {@inheritDoc}
    */
    /** {@inheritDoc} */
    @Override
    public Collection<ExtensibleIndexer> getIndexers(
        IndexingOptions indexingOptions)
@@ -401,7 +330,7 @@
      {
        indexer = new RelativeTimeExtensibleIndexer(this);
      }
       return Collections.singletonList(indexer);
      return Collections.singletonList(indexer);
    }
  }
@@ -414,7 +343,7 @@
  private final class RelativeTimeGTOrderingMatchingRule
          extends RelativeTimeOrderingMatchingRule
  {
    //All the names for this matching rule.
    /** All the names for this matching rule. */
    private final List<String> names;
@@ -436,9 +365,7 @@
    }
    /**
      * {@inheritDoc}
      */
    /** {@inheritDoc} */
    @Override
    public Collection<String> getNames()
    {
@@ -447,9 +374,7 @@
    /**
      * {@inheritDoc}
      */
    /** {@inheritDoc} */
    @Override
    public String getOID()
    {
@@ -458,9 +383,7 @@
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public ConditionResult valuesMatch(ByteSequence attributeValue,
        ByteSequence assertionValue)
@@ -469,18 +392,37 @@
      return ConditionResult.valueOf(ret > 0);
    }
    /** {@inheritDoc} */
    @Override
    public Assertion getAssertion(final ByteSequence value)
        throws DecodeException
    {
      final ByteString assertionValue = normalizeAssertionValue(value);
      return new Assertion()
      {
        @Override
        public ConditionResult matches(ByteSequence attributeValue)
        {
          return valuesMatch(attributeValue, assertionValue);
        }
        @Override
        public <T> T createIndexQuery(IndexQueryFactory<T> factory)
            throws DecodeException
        {
          return factory.createRangeMatchQuery(indexer.getExtensibleIndexID(),
              assertionValue, ByteString.empty(), false, false);
        }
      };
    }
    /**
    * {@inheritDoc}
    */
    /** {@inheritDoc} */
    @Override
    public <T> T createIndexQuery(ByteSequence assertionValue,
        IndexQueryFactory<T> factory) throws DecodeException
    {
      return factory.createRangeMatchQuery(indexer
          .getExtensibleIndexID(), normalizeAssertionValue(assertionValue),
          ByteString.empty(), false, false);
      return getAssertion(assertionValue).createIndexQuery(factory);
    }
  }
@@ -493,7 +435,7 @@
  private final class RelativeTimeLTOrderingMatchingRule
          extends RelativeTimeOrderingMatchingRule
  {
    //All the names for this matching rule.
    /** All the names for this matching rule. */
    private final List<String> names;
@@ -516,9 +458,7 @@
    }
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public Collection<String> getNames()
    {
@@ -527,9 +467,7 @@
    /**
      * {@inheritDoc}
      */
    /** {@inheritDoc} */
    @Override
    public String getOID()
    {
@@ -538,9 +476,7 @@
     /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public ConditionResult valuesMatch(ByteSequence attributeValue,
        ByteSequence assertionValue)
@@ -549,17 +485,36 @@
      return ConditionResult.valueOf(ret < 0);
    }
    /** {@inheritDoc} */
    @Override
    public Assertion getAssertion(final ByteSequence value)
        throws DecodeException
    {
      final ByteString assertionValue = normalizeAssertionValue(value);
      return new Assertion()
      {
        @Override
        public ConditionResult matches(ByteSequence attributeValue)
        {
          return valuesMatch(attributeValue, assertionValue);
        }
    /**
    * {@inheritDoc}
    */
        @Override
        public <T> T createIndexQuery(IndexQueryFactory<T> factory)
            throws DecodeException
        {
          return factory.createRangeMatchQuery(indexer.getExtensibleIndexID(),
              ByteString.empty(), assertionValue, false, false);
        }
      };
    }
    /** {@inheritDoc} */
    @Override
    public <T> T createIndexQuery(ByteSequence assertionValue,
        IndexQueryFactory<T> factory) throws DecodeException
    {
      return factory.createRangeMatchQuery(indexer
          .getExtensibleIndexID(), ByteString.empty(),
          normalizeAssertionValue(assertionValue),false, false);
      return getAssertion(assertionValue).createIndexQuery(factory);
    }
  }
@@ -594,9 +549,7 @@
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public String getExtensibleIndexID()
    {
@@ -605,9 +558,7 @@
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public final void createKeys(Schema schema, ByteSequence value2,
        IndexingOptions options, Collection<ByteString> keys)
@@ -632,16 +583,14 @@
  private final class PartialDateAndTimeMatchingRule
          extends TimeBasedMatchingRule
  {
     /**
      * Indexer associated with this instance.
      */
     private ExtensibleIndexer indexer;
    /**
     * {@inheritDoc}
     * Indexer associated with this instance.
     */
    private ExtensibleIndexer indexer;
    /** {@inheritDoc} */
    @Override
    public String getOID()
    {
@@ -650,9 +599,7 @@
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public Collection<String> getNames()
    {
@@ -661,9 +608,7 @@
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public ByteString normalizeAssertionValue(ByteSequence value)
        throws DecodeException
@@ -684,13 +629,15 @@
      into a format to be recognized by the compare routine. The normalized
      value is actually the format of : smhDMY.
      */
      int second = -1;
      int minute = -1;
      int hour = -1;
      int date = 0;
      int year = 0;
      final int initDate = 0;
      final int initVal = -1;
      int second = initVal;
      int minute = initVal;
      int hour = initVal;
      int date = initDate;
      int month = initVal;
      int year = initDate;
      int number = 0;
      int month = -1;
      int length = value.length();
      for(int index=0; index<length; index++)
@@ -698,56 +645,15 @@
        byte b = value.byteAt(index);
        if(isDigit((char)b))
        {
          switch (value.byteAt(index))
          {
            case '0':
              number = (number * 10);
              break;
            case '1':
              number = (number * 10) + 1;
              break;
            case '2':
              number = (number * 10) + 2;
              break;
            case '3':
              number = (number * 10) + 3;
              break;
            case '4':
              number = (number * 10) + 4;
              break;
            case '5':
              number = (number * 10) + 5;
              break;
            case '6':
              number = (number * 10) + 6;
              break;
            case '7':
              number = (number * 10) + 7;
              break;
            case '8':
              number = (number * 10) + 8;
              break;
            case '9':
              number = (number * 10) + 9;
              break;
          }
          number = multiplyByTenThenAddUnits(number, b);
        }
        else
        {
          LocalizableMessage message = null;
          switch(value.byteAt(index))
          switch(b)
          {
            case 's':
              if(second >0)
              if (second != initVal)
              {
                 message = WARN_ATTR_DUPLICATE_SECOND_ASSERTION_FORMAT.get(value, date);
              }
@@ -757,7 +663,7 @@
              }
              break;
            case 'm':
              if(minute >0)
              if (minute != initVal)
              {
                 message = WARN_ATTR_DUPLICATE_MINUTE_ASSERTION_FORMAT.get(value, date);
              }
@@ -767,7 +673,7 @@
              }
              break;
            case 'h':
              if(hour >0)
              if (hour != initVal)
              {
                 message = WARN_ATTR_DUPLICATE_HOUR_ASSERTION_FORMAT.get(value, date);
              }
@@ -781,7 +687,7 @@
              {
                message = WARN_ATTR_INVALID_DATE_ASSERTION_FORMAT.get(value, number);
              }
              else if(date > 0)
            else if (date != initDate)
              {
                message = WARN_ATTR_DUPLICATE_DATE_ASSERTION_FORMAT.get(value, date);
              }
@@ -791,11 +697,11 @@
              }
              break;
            case 'M':
               if(number == 0)
              if (number == 0)
              {
                message = WARN_ATTR_INVALID_MONTH_ASSERTION_FORMAT.get(value, number);
              }
              else if(month > 0)
              else if (month != initVal)
              {
                message = WARN_ATTR_DUPLICATE_MONTH_ASSERTION_FORMAT.get(value, month);
              }
@@ -809,7 +715,7 @@
              {
                message = WARN_ATTR_INVALID_YEAR_ASSERTION_FORMAT.get(value, number);
              }
              else if(year >0)
              else if (year != initDate)
              {
                message = WARN_ATTR_DUPLICATE_YEAR_ASSERTION_FORMAT.get(value, year);
              }
@@ -819,133 +725,45 @@
              }
              break;
            default:
                message = WARN_ATTR_INVALID_PARTIAL_TIME_ASSERTION_FORMAT.
                        get(value,(char)value.byteAt(index));
              message = WARN_ATTR_INVALID_PARTIAL_TIME_ASSERTION_FORMAT.get(value, (char) b);
          }
          if(message !=null)
          {
            logger.error(message);
            throw DecodeException.error(message);
          }
          else
          {
            number = 0;
          }
          number = 0;
        }
      }
      month = toCalendarMonth(month, value);
      //Validate year, month , date , hour, minute and second in that order.
      if(year < 0)
      // -1 values are allowed when these values have not been provided
      if (year < 0)
      {
        //A future date is allowed.
        LocalizableMessage message = WARN_ATTR_INVALID_YEAR_ASSERTION_FORMAT.get(value, year);
        logger.warn(message);
        throw DecodeException.error(message);
        logAndThrow(WARN_ATTR_INVALID_YEAR_ASSERTION_FORMAT.get(value, year));
      }
      if (isDateInvalid(date, month, year))
      {
        logAndThrow(WARN_ATTR_INVALID_DATE_ASSERTION_FORMAT.get(value, date));
      }
      if (hour < initVal || hour > 23)
      {
        logAndThrow(WARN_ATTR_INVALID_HOUR_ASSERTION_FORMAT.get(value, hour));
      }
      if (minute < initVal || minute > 59)
      {
        logAndThrow(WARN_ATTR_INVALID_MINUTE_ASSERTION_FORMAT.get(value, minute));
      }
      if (second < initVal || second > 60) // Consider leap seconds.
      {
        logAndThrow(WARN_ATTR_INVALID_SECOND_ASSERTION_FORMAT.get(value, second));
      }
      switch(month)
      {
        case -1:
          //just allow this.
          break;
        case 1:
          month = Calendar.JANUARY;
          break;
        case 2:
          month = Calendar.FEBRUARY;
          break;
        case 3:
          month = Calendar.MARCH;
          break;
        case 4:
          month = Calendar.APRIL;
          break;
        case 5:
          month = Calendar.MAY;
          break;
        case 6:
          month = Calendar.JUNE;
          break;
        case 7:
          month = Calendar.JULY;
          break;
        case 8:
          month = Calendar.AUGUST;
          break;
        case 9:
          month = Calendar.SEPTEMBER;
          break;
        case 10:
          month = Calendar.OCTOBER;
          break;
        case 11:
          month = Calendar.NOVEMBER;
          break;
        case 12:
          month = Calendar.DECEMBER;
          break;
        default:
          LocalizableMessage message = WARN_ATTR_INVALID_MONTH_ASSERTION_FORMAT.get(value, month);
          logger.warn(message);
          throw DecodeException.error(message);
      }
      boolean invalidDate = false;
      switch(date)
      {
        case 29:
          if(month == Calendar.FEBRUARY && year%4 !=0)
          {
            invalidDate = true;
          }
          break;
        case 31:
          if(month != -1 && month != Calendar.JANUARY && month!= Calendar.MARCH
                  && month != Calendar.MAY && month != Calendar.JULY
                  && month != Calendar.AUGUST && month != Calendar.OCTOBER
                  && month != Calendar.DECEMBER)
          {
            invalidDate = true;
          }
          break;
        default:
          if(!(date >=0 && date <=31))
          {
            invalidDate = true;
          }
      }
      if(invalidDate)
      {
        LocalizableMessage message = WARN_ATTR_INVALID_DATE_ASSERTION_FORMAT.get(value, date);
        logger.warn(message);
        throw DecodeException.error(message);
      }
      if(!(hour >=-1 && hour <=23))
      {
        LocalizableMessage message = WARN_ATTR_INVALID_HOUR_ASSERTION_FORMAT.get(value, date);
        logger.warn(message);
        throw DecodeException.error(message);
      }
      if(!(minute >=-1 && minute <=59))
      {
        LocalizableMessage message = WARN_ATTR_INVALID_MINUTE_ASSERTION_FORMAT.get(value, date);
        logger.warn(message);
        throw DecodeException.error(message);
      }
      if(!(second >=-1 && second <=60)) //Consider leap seconds.
      {
        LocalizableMessage message = WARN_ATTR_INVALID_SECOND_ASSERTION_FORMAT.get(value, date);
        logger.warn(message);
        throw DecodeException.error(message);
      }
      /**
       * Since we reached here we have a valid assertion value. Construct
       * a normalized value in the order: SECOND MINUTE HOUR  DATE MONTH YEAR.
       */
      // Since we reached here we have a valid assertion value.
      // Construct a normalized value in the order: SECOND MINUTE HOUR  DATE MONTH YEAR.
      ByteBuffer bb = ByteBuffer.allocate(6*4);
      bb.putInt(second);
      bb.putInt(minute);
@@ -956,20 +774,90 @@
      return ByteString.wrap(bb.array());
    }
    private void logAndThrow(LocalizableMessage message) throws DecodeException
    {
      logger.warn(message);
      throw DecodeException.error(message);
    }
    private boolean isDateInvalid(int date, int month, int year)
    {
      switch (date)
      {
      case 29:
        return month == Calendar.FEBRUARY && !isLeapYear(year);
      case 30:
        return month == Calendar.FEBRUARY;
      case 31:
        return month != -1 && month != Calendar.JANUARY
            && month != Calendar.MARCH && month != Calendar.MAY
            && month != Calendar.JULY && month != Calendar.AUGUST
            && month != Calendar.OCTOBER && month != Calendar.DECEMBER;
      default:
        return date < 0 || date > 31;
      }
    }
     /**
     * {@inheritDoc}
     */
    private boolean isLeapYear(int year)
    {
      if (year % 400 == 0)
      {
        return true;
      }
      if (year % 100 == 0)
      {
        return false;
      }
      return year % 4 == 0;
    }
    private int toCalendarMonth(int month, ByteSequence value) throws DecodeException
    {
      switch (month)
      {
      case -1:
        // just allow this.
        return -1;
      case 1:
        return Calendar.JANUARY;
      case 2:
        return Calendar.FEBRUARY;
      case 3:
        return Calendar.MARCH;
      case 4:
        return Calendar.APRIL;
      case 5:
        return Calendar.MAY;
      case 6:
        return Calendar.JUNE;
      case 7:
        return Calendar.JULY;
      case 8:
        return Calendar.AUGUST;
      case 9:
        return Calendar.SEPTEMBER;
      case 10:
        return Calendar.OCTOBER;
      case 11:
        return Calendar.NOVEMBER;
      case 12:
        return Calendar.DECEMBER;
      default:
        LocalizableMessage message = WARN_ATTR_INVALID_MONTH_ASSERTION_FORMAT.get(value, month);
        logger.warn(message);
        throw DecodeException.error(message);
      }
    }
    /** {@inheritDoc} */
    @Override
    public ConditionResult valuesMatch(ByteSequence attributeValue,
        ByteSequence assertionValue)
    {
      long timeInMS = ((ByteString)attributeValue).toLong();
      //Build the information from the attribute value.
      // Build the information from the attribute value.
      GregorianCalendar cal = new GregorianCalendar(TIME_ZONE_UTC_OBJ);
      cal.setLenient(false);
      cal.setTimeInMillis(timeInMS);
      cal.setTimeInMillis(((ByteString) attributeValue).toLong());
      int second = cal.get(Calendar.SECOND);
      int minute = cal.get(Calendar.MINUTE);
      int hour = cal.get(Calendar.HOUR_OF_DAY);
@@ -977,7 +865,6 @@
      int month = cal.get(Calendar.MONTH);
      int year = cal.get(Calendar.YEAR);
      //Build the information from the assertion value.
      ByteBuffer bb = ByteBuffer.wrap(assertionValue.toByteArray());
      int assertSecond = bb.getInt(0);
@@ -987,10 +874,10 @@
      int assertMonth = bb.getInt(16);
      int assertYear = bb.getInt(20);
      // All the non-zero and non -1 values should match.
      if ((assertSecond != -1 && assertSecond != second)
          || (assertMinute != -1 && assertMinute != minute)
          || (assertHour != -1 && assertHour != hour)
          // All the non-zero values should match.
          || (assertDate != 0 && assertDate != date)
          || (assertMonth != -1 && assertMonth != month)
          || (assertYear != 0 && assertYear != year))
@@ -1002,9 +889,7 @@
    /**
      * {@inheritDoc}
      */
    /** {@inheritDoc} */
    @Override
    public Collection<ExtensibleIndexer> getIndexers(
        IndexingOptions indexingOptions)
@@ -1018,9 +903,7 @@
    /**
      * {@inheritDoc}
      */
    /** {@inheritDoc} */
    @Override
    public <T> T createIndexQuery(ByteSequence assertionValue,
            IndexQueryFactory<T> factory) throws DecodeException
@@ -1035,52 +918,41 @@
      int assertDate = bb.getInt(12);
      int assertMonth = bb.getInt(16);
      int assertYear = bb.getInt(20);
      List<T> queries = new ArrayList<T>();
      List<T> queries = new ArrayList<T>();
      if(assertSecond >= 0)
      {
        queries.add(factory.createExactMatchQuery(
                indexer.getExtensibleIndexID(),
               getKey(assertSecond,SECOND)));
        queries.add(createExactMatchQuery(factory, assertSecond, SECOND));
      }
      if(assertMinute >=0)
      {
         queries.add(factory.createExactMatchQuery(
                indexer.getExtensibleIndexID(),
               getKey(assertMinute,MINUTE)));
        queries.add(createExactMatchQuery(factory, assertMinute, MINUTE));
      }
      if(assertHour >=0)
      {
         queries.add(factory.createExactMatchQuery(
                indexer.getExtensibleIndexID(),
               getKey(assertHour,HOUR)));
        queries.add(createExactMatchQuery(factory, assertHour, HOUR));
      }
      if(assertDate >0)
      {
        queries.add(factory.createExactMatchQuery(
                indexer.getExtensibleIndexID(),
                getKey(assertDate,DATE)));
        queries.add(createExactMatchQuery(factory, assertDate, DATE));
      }
      if(assertMonth >=0)
      {
        queries.add(factory.createExactMatchQuery(
                indexer.getExtensibleIndexID(),
                getKey(assertMonth,MONTH)));
        queries.add(createExactMatchQuery(factory, assertMonth, MONTH));
      }
      if(assertYear > 0)
      {
        queries.add(factory.createExactMatchQuery(
                indexer.getExtensibleIndexID(),
                getKey(assertYear,YEAR)));
        queries.add(createExactMatchQuery(factory, assertYear, YEAR));
      }
      return factory.createIntersectionQuery(queries);
    }
    private <T> T createExactMatchQuery(IndexQueryFactory<T> factory,
        int assertionValue, char type)
    {
      return factory.createExactMatchQuery(
          indexer.getExtensibleIndexID(), getKey(assertionValue, type));
    }
    /**
@@ -1135,6 +1007,33 @@
    }
  }
  private int multiplyByTenThenAddUnits(int number, byte b)
  {
    switch (b)
    {
    case '0':
      return number * 10;
    case '1':
      return number * 10 + 1;
    case '2':
      return number * 10 + 2;
    case '3':
      return number * 10 + 3;
    case '4':
      return number * 10 + 4;
    case '5':
      return number * 10 + 5;
    case '6':
      return number * 10 + 6;
    case '7':
      return number * 10 + 7;
    case '8':
      return number * 10 + 8;
    case '9':
      return number * 10 + 9;
    }
    return number;
  }
   /**
@@ -1143,7 +1042,7 @@
  private final class PartialDateAndTimeExtensibleIndexer extends
      ExtensibleIndexer
  {
    // The partial date and Time matching Rule.
    /** The partial date and Time matching Rule. */
    private final PartialDateAndTimeMatchingRule matchingRule;
@@ -1162,9 +1061,7 @@
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public void createKeys(Schema schema, ByteSequence value,
        IndexingOptions options, Collection<ByteString> keys)
@@ -1181,9 +1078,7 @@
    /**
     * {@inheritDoc}
     */
    /** {@inheritDoc} */
    @Override
    public String getExtensibleIndexID()
    {
opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/TimeBasedMatchingRuleFactoryTest.java
New file
@@ -0,0 +1,234 @@
/*
 * 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.opends.server.schema;
import java.text.SimpleDateFormat;
import java.util.*;
import org.forgerock.opendj.ldap.Assertion;
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.CollationMatchingRuleCfg;
import org.opends.server.admin.std.server.MatchingRuleCfg;
import org.opends.server.api.MatchingRule;
import org.opends.server.util.TimeThread;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
@SuppressWarnings("javadoc")
public class TimeBasedMatchingRuleFactoryTest extends SchemaTestCase
{
  private static final String LESS_THAN_RELATIVE_TIME = "relativeTimeLTOrderingMatch";
  private static final String GREATER_THAN_RELATIVE_TIME = "relativeTimeGTOrderingMatch";
  private static final String PARTIAL_DATE_AND_TIME = "partialDateAndTimeMatchingRule";
  @DataProvider
  public Iterator<Object[]> validAssertionValuesDataProvider()
  {
    final SimpleDateFormat generalizedTimeFormatter = new SimpleDateFormat("yyyyMMddHHmmss'Z'");
    final Calendar cal = TimeThread.getCalendar();
    final Date nowDate = cal.getTime();
    final String nowGT = generalizedTimeFormatter.format(nowDate);
    cal.add(Calendar.MONTH, 1);
    final Date oneMonthAheadDate = cal.getTime();
    final String oneMonthAheadGT = generalizedTimeFormatter.format(oneMonthAheadDate);
    final Collection<Object[]> results = new LinkedList<Object[]>(Arrays.asList(new Object[][] {
          { LESS_THAN_RELATIVE_TIME,    /* now + */"1"/* s */, oneMonthAheadGT, ConditionResult.FALSE },
          { GREATER_THAN_RELATIVE_TIME, /* now + */"1"/* s */, oneMonthAheadGT, ConditionResult.TRUE },
          { LESS_THAN_RELATIVE_TIME,    /* now */"+1s", oneMonthAheadGT, ConditionResult.FALSE },
          { GREATER_THAN_RELATIVE_TIME, /* now */"+1s", oneMonthAheadGT, ConditionResult.TRUE },
          { LESS_THAN_RELATIVE_TIME,    /* now */"+1m", oneMonthAheadGT, ConditionResult.FALSE },
          { GREATER_THAN_RELATIVE_TIME, /* now */"+1m", oneMonthAheadGT, ConditionResult.TRUE },
          { LESS_THAN_RELATIVE_TIME,    /* now */"+1h", oneMonthAheadGT, ConditionResult.FALSE },
          { GREATER_THAN_RELATIVE_TIME, /* now */"+1h", oneMonthAheadGT, ConditionResult.TRUE },
          { LESS_THAN_RELATIVE_TIME,    /* now */"-30d", nowGT, ConditionResult.FALSE },
          { GREATER_THAN_RELATIVE_TIME, /* now */"-30d", nowGT, ConditionResult.TRUE },
          { LESS_THAN_RELATIVE_TIME,    /* now */"-30w", nowGT, ConditionResult.FALSE },
          { GREATER_THAN_RELATIVE_TIME, /* now */"-30w", nowGT, ConditionResult.TRUE },
          // 29th of months and leap years
          { PARTIAL_DATE_AND_TIME, "2012Y03M29D", "20120329120000Z", ConditionResult.TRUE },
          { PARTIAL_DATE_AND_TIME, "2012Y02M29D", "20120229120000Z", ConditionResult.TRUE },
          { PARTIAL_DATE_AND_TIME, "2000Y02M29D", "20000229120000Z", ConditionResult.TRUE },
          // Generalized time implementation does not allow leap seconds
          // because Java does not support them. Apparently, it never will support them:
          // @see http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4272347
          // leap seconds are allowed, even though no formula exists to validate them
          { PARTIAL_DATE_AND_TIME, "2012Y06M30D23h59m60s", "20120630235959Z", ConditionResult.FALSE },
          // no match
          { PARTIAL_DATE_AND_TIME, "2012Y12M31D23h59m30s", "20111231235930Z", ConditionResult.FALSE },
          { PARTIAL_DATE_AND_TIME, "2012Y12M31D23h59m30s", "20121031235930Z", ConditionResult.FALSE },
          { PARTIAL_DATE_AND_TIME, "2012Y12M31D23h59m30s", "20121230235930Z", ConditionResult.FALSE },
          { PARTIAL_DATE_AND_TIME, "2012Y12M31D23h59m30s", "20121231225930Z", ConditionResult.FALSE },
          { PARTIAL_DATE_AND_TIME, "2012Y12M31D23h59m30s", "20121231235830Z", ConditionResult.FALSE },
          { PARTIAL_DATE_AND_TIME, "2012Y12M31D23h59m30s", "20121231235829Z", ConditionResult.FALSE },
          // 30th of months
          { PARTIAL_DATE_AND_TIME, "1982Y09M30D", "19820930120000Z", ConditionResult.TRUE },
          // 31st of months
          { PARTIAL_DATE_AND_TIME, "2012Y01M31D", "20120131120000Z", ConditionResult.TRUE },
          { PARTIAL_DATE_AND_TIME, "2012Y03M31D", "20120331120000Z", ConditionResult.TRUE },
          { PARTIAL_DATE_AND_TIME, "2012Y05M31D", "20120531120000Z", ConditionResult.TRUE },
          { PARTIAL_DATE_AND_TIME, "2012Y07M31D", "20120731120000Z", ConditionResult.TRUE },
          { PARTIAL_DATE_AND_TIME, "2012Y08M31D", "20120831120000Z", ConditionResult.TRUE },
          { PARTIAL_DATE_AND_TIME, "2012Y10M31D", "20121031120000Z", ConditionResult.TRUE },
          { PARTIAL_DATE_AND_TIME, "2012Y12M31D", "20121231120000Z", ConditionResult.TRUE },
          // Only single time units
          { PARTIAL_DATE_AND_TIME, "2012Y", "20121231123000Z", ConditionResult.TRUE },
          { PARTIAL_DATE_AND_TIME, "2012Y12M", "20121231123000Z", ConditionResult.TRUE },
          { PARTIAL_DATE_AND_TIME, "2012Y31D", "20121231123000Z", ConditionResult.TRUE },
          { PARTIAL_DATE_AND_TIME, "2012Y12h", "20121231123000Z", ConditionResult.TRUE },
          { PARTIAL_DATE_AND_TIME, "2012Y30m", "20121231123000Z", ConditionResult.TRUE },
          { PARTIAL_DATE_AND_TIME, "2012Y0s", "20121231123000Z", ConditionResult.TRUE },
    }));
    addPartialDateAndTimeData(results, nowDate, oneMonthAheadDate);
    return results.iterator();
  }
  private void addPartialDateAndTimeData(Collection<Object[]> results, Date... dates)
  {
    final SimpleDateFormat ptFormatterUpToSeconds = new SimpleDateFormat("yyyy'Y'MM'M'dd'D'HH'h'mm'm'ss's'");
    final SimpleDateFormat ptFormatterUpToMinutes = new SimpleDateFormat("yyyy'Y'MM'M'dd'D'HH'h'mm'm'");
    final SimpleDateFormat ptFormatterUpToHours = new SimpleDateFormat("yyyy'Y'MM'M'dd'D'HH'h'");
    final SimpleDateFormat gtFormatterUpToSeconds = new SimpleDateFormat("yyyyMMddHHmmss'Z'");
    final SimpleDateFormat gtFormatterUpToMinutes = new SimpleDateFormat("yyyyMMddHHmm'Z'");
    final SimpleDateFormat gtFormatterUpToHours = new SimpleDateFormat("yyyyMMddHH'Z'");
    for (Date date : dates)
    {
      String ptUpToSeconds = ptFormatterUpToSeconds.format(date);
      String gtUpToSeconds = gtFormatterUpToSeconds.format(date);
      results.add(new Object[] { PARTIAL_DATE_AND_TIME, ptUpToSeconds, gtUpToSeconds, ConditionResult.TRUE });
      String ptUpToMinutes = ptFormatterUpToMinutes.format(date);
      String gtUpToMinutes = gtFormatterUpToMinutes.format(date);
      results.add(new Object[] { PARTIAL_DATE_AND_TIME, ptUpToMinutes, gtUpToMinutes, ConditionResult.TRUE });
      String ptUpToHours = ptFormatterUpToHours.format(date);
      String gtUpToHours = gtFormatterUpToHours.format(date);
      results.add(new Object[] { PARTIAL_DATE_AND_TIME, ptUpToHours, gtUpToHours, ConditionResult.TRUE });
    }
  }
  @DataProvider
  public Object[][] invalidAssertionValuesDataProvider()
  {
    final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyyMMddHH");
    final Calendar cal = TimeThread.getCalendar();
    final String now = dateFormatter.format(cal.getTime()) + "Z";
    return new Object[][] {
      // { LESS_THAN_RELATIVE_TIME, "", now, null },
      { LESS_THAN_RELATIVE_TIME, "bla", now, null },
      { LESS_THAN_RELATIVE_TIME, /* now */"-30b", now, null },
      { LESS_THAN_RELATIVE_TIME, /* now */"-30ms", now, null },
      // { PARTIAL_DATE_AND_TIME, "", now, null },
      { PARTIAL_DATE_AND_TIME, "bla", now, null },
      // invalid time unit values
      { PARTIAL_DATE_AND_TIME, "-1Y03M11D12h48m32s", now, null },
      { PARTIAL_DATE_AND_TIME, "0Y03M11D12h48m32s", now, null },
      { PARTIAL_DATE_AND_TIME, "2014Y-1M11D12h48m32s", now, null },
      { PARTIAL_DATE_AND_TIME, "2014Y0M11D12h48m32s", now, null },
      { PARTIAL_DATE_AND_TIME, "2014Y13M11D12h48m32s", now, null },
      { PARTIAL_DATE_AND_TIME, "2014Y03M-1D12h48m32s", now, null },
      { PARTIAL_DATE_AND_TIME, "2014Y03M0D12h48m32s", now, null },
      { PARTIAL_DATE_AND_TIME, "2014Y13M32D12h48m32s", now, null },
      { PARTIAL_DATE_AND_TIME, "2014Y03M11D-1h48m32s", now, null },
      { PARTIAL_DATE_AND_TIME, "2014Y03M11D24h48m32s", now, null },
      { PARTIAL_DATE_AND_TIME, "2014Y03M11D12h-1m32s", now, null },
      { PARTIAL_DATE_AND_TIME, "2014Y03M11D12h60m32s", now, null },
      { PARTIAL_DATE_AND_TIME, "2014Y03M11D12h48m-1s", now, null },
      { PARTIAL_DATE_AND_TIME, "2014Y03M11D12h48m61s", now, null },
      // duplicate each time unit
      { PARTIAL_DATE_AND_TIME, "1Y2014Y03M11D12h", now, null },
      { PARTIAL_DATE_AND_TIME, "2014Y1M03M11D12h", now, null },
      { PARTIAL_DATE_AND_TIME, "2014Y03M1D11D12h", now, null },
      { PARTIAL_DATE_AND_TIME, "2014Y03M11D1h12h", now, null },
      { PARTIAL_DATE_AND_TIME, "2014Y03M11D12h1m48m", now, null },
      { PARTIAL_DATE_AND_TIME, "2014Y03M11D12h48m1s32s", now, null },
      // February and non leap years
      { PARTIAL_DATE_AND_TIME, "2014Y02M29D", now, null },
      { PARTIAL_DATE_AND_TIME, "1800Y02M29D", now, null },
      { PARTIAL_DATE_AND_TIME, "2000Y02M30D", now, null },
      { PARTIAL_DATE_AND_TIME, "2000Y02M31D", now, null },
      // 31st of months
      { PARTIAL_DATE_AND_TIME, "2012Y04M31D", now, null },
      { PARTIAL_DATE_AND_TIME, "2012Y06M31D", now, null },
      { PARTIAL_DATE_AND_TIME, "2012Y09M31D", now, null },
      { PARTIAL_DATE_AND_TIME, "2012Y11M31D", now, null },
    };
  }
  @Test(dataProvider = "validAssertionValuesDataProvider")
  public void testValidAssertionValues(String matchingRuleName,
      String assertionValue, String attributeValue,
      ConditionResult expectedResult) throws Exception
  {
    final MatchingRule rule = getMatchingRule(matchingRuleName);
    Assertion assertion = rule.getAssertion(ByteString.valueOf(assertionValue));
    ByteString normalizedAttributeValue =
        rule.normalizeAttributeValue(ByteString.valueOf(attributeValue));
    assertThat(assertion.matches(normalizedAttributeValue)).isEqualTo(expectedResult);
  }
  @Test(dataProvider = "invalidAssertionValuesDataProvider",
      expectedExceptions = DecodeException.class)
  public void testInvalidAssertionValues(String matchingRuleName,
      String assertionValue, String attributeValue,
      ConditionResult expectedResult) throws Exception
  {
    testValidAssertionValues(matchingRuleName, assertionValue, attributeValue, expectedResult);
  }
  private MatchingRule getMatchingRule(String matchingRuleName) throws Exception
  {
    final Collection<MatchingRule> mRules = getMatchingRules();
    assertThat(mRules).hasSize(3);
    for (MatchingRule mRule : mRules)
    {
      if (mRule.getNameOrOID().equals(matchingRuleName))
      {
        return mRule;
      }
    }
    fail("Could not find a matching rule named '" + matchingRuleName + "'");
    return null;
  }
  private Collection<MatchingRule> getMatchingRules() throws Exception
  {
    final TimeBasedMatchingRuleFactory factory =
        new TimeBasedMatchingRuleFactory();
    final MatchingRuleCfg cfg = mock(CollationMatchingRuleCfg.class);
    factory.initializeMatchingRule(cfg);
    final Collection<MatchingRule> mRules = factory.getMatchingRules();
    verifyNoMoreInteractions(cfg);
    return mRules;
  }
}
opendj3-server-dev/tests/unit-tests-testng/src/server/org/opends/server/schema/TimeBasedMatchingRuleTest.java
@@ -31,6 +31,7 @@
import java.util.List;
import java.util.TimeZone;
import org.assertj.core.api.Assertions;
import org.forgerock.opendj.ldap.*;
import org.opends.server.TestCaseUtils;
import org.opends.server.api.MatchingRule;
@@ -107,7 +108,7 @@
    "objectclasses: ( testoc-oid NAME 'testOC' SUP top AUXILIARY MUST test-time)",
    "objectclasses: ( testoc2-oid NAME 'testOC2' SUP top AUXILIARY MUST test-date)"
    );
    assertTrue(resultCode == 0);
    assertEquals(0, resultCode);
  }
@@ -222,7 +223,8 @@
      searchOperation.run();
      assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS);
      List<SearchResultEntry> entries = searchOperation.getSearchEntries();
      assertTrue(entries.size()==3 && dnFoundInEntryList(entries,user3,user4,user5));
      Assertions.assertThat(entries).hasSize(3);
      assertTrue(dnFoundInEntryList(entries, user3, user4, user5));
    }
    finally
    {
@@ -479,30 +481,27 @@
    };
  }
//validate if the args are found in the entries list.
  private boolean dnFoundInEntryList( List<SearchResultEntry> entries,DN ... dns)
  // validate if the args are found in the entries list.
  private boolean dnFoundInEntryList(List<SearchResultEntry> entries, DN... dns)
  {
    for(DN dn: dns)
    for (DN dn : dns)
    {
      boolean found = false;
      for(SearchResultEntry entry: entries)
      {
        System.out.println("dn from the current entry is " + entry.getName());
        if(entry.getName().equals(dn))
        {
          found = true;
        }
      }
      if(!found)
      {
        return false;
      }
      assertTrue(find(entries, dn), "Could not find dn " + dn + " in list " + entries);
    }
    return true;
  }
  private boolean find(List<SearchResultEntry> entries, DN dn)
  {
    for (SearchResultEntry entry : entries)
    {
      if (entry.getName().equals(dn))
      {
        return true;
      }
    }
    return false;
  }
  //Creates the entries.
  private void populateEntries() throws Exception