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

sin
02.40.2009 86c4cbb066147cad498b37d2e34e231518b907ef
Issue 4075 : Provide implementations for the relative and partial time matching rules
2 files added
5 files modified
2017 ■■■■■ changed files
opends/resource/config/config.ldif 8 ●●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif 5 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/schema.properties 36 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/schema/SchemaConstants.java 86 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/schema/TimeBasedMatchingRuleFactory.java 1366 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/ServerConstants.java 8 ●●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/schema/TimeBasedMatchingRuleTest.java 508 ●●●●● patch | view | raw | blame | history
opends/resource/config/config.ldif
@@ -1303,6 +1303,14 @@
ds-cfg-java-class: org.opends.server.schema.TelephoneNumberSubstringMatchingRuleFactory
ds-cfg-enabled: true
dn: cn=Time Based Matching Rule,cn=Matching Rules,cn=config
objectClass: top
objectClass: ds-cfg-matching-rule
objectClass: ds-cfg-extensible-matching-rule
cn: Time Based Matching Rule
ds-cfg-java-class: org.opends.server.schema.TimeBasedMatchingRuleFactory
ds-cfg-enabled: true
dn: cn=Unique Member Equality Matching Rule,cn=Matching Rules,cn=config
objectClass: top
objectClass: ds-cfg-matching-rule
opends/resource/schema/02-config.ldif
@@ -4049,4 +4049,9 @@
  SUP ds-cfg-virtual-attribute
  STRUCTURAL
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.231
  NAME 'ds-cfg-extensible-matching-rule'
  SUP ds-cfg-matching-rule
  STRUCTURAL
  X-ORIGIN 'OpenDS Directory Server' )
opends/src/messages/messages/schema.properties
@@ -960,3 +960,39 @@
 value could not be parsed as a valid superior object class definition because \
 definition for the objectclass with OID %s has already  declared a superior objectclass with an OID \
 of %s. Multiple inheritance of objectclasses is not yet supported
MILD_WARN_ATTR_INVALID_RELATIVE_TIME_ASSERTION_FORMAT_294=The provided \
 value "%s" could not be parsed as a valid assertion value because the \
 character '%c' is not allowed. The acceptable values are s(second),m(minute), \
 ,h(hour),d(day) and w(week)
MILD_WARN_ATTR_INVALID_PARTIAL_TIME_ASSERTION_FORMAT_295=The provided \
 value "%s" could not be parsed as a valid assertion value because the \
 character '%c' is not allowed. The acceptable values are DD (date),MM(month) \
 and YYYY(year)
MILD_WARN_ATTR_MISSING_CHAR_PARTIAL_TIME_ASSERTION_FORMAT_296=The provided \
 value "%s" could not be parsed as a valid assertion value because an invalid \
 character '%c' is  found instead of '%c' at position %d
MILD_WARN_ATTR_INVALID_DATE_ASSERTION_FORMAT_297=The provided \
  value "%s" could not be parsed as a valid assertion value because "%d" is not \
  a valid date specification
MILD_WARN_ATTR_INVALID_MONTH_ASSERTION_FORMAT_298=The provided \
  value "%s" could not be parsed as a valid assertion value because "%d" is not \
  a valid month specification
MILD_WARN_ATTR_INVALID_YEAR_ASSERTION_FORMAT_299=The provided \
  value "%s" could not be parsed as a valid assertion value because "%d" is not \
  a valid year specification
MILD_WARN_ATTR_DUPLICATE_DATE_ASSERTION_FORMAT_300=The provided \
  value "%s" could not be parsed as a valid assertion value because there is  \
  conflicting  value "%d" for DD(Date) specification
MILD_WARN_ATTR_DUPLICATE_MONTH_ASSERTION_FORMAT_301=The provided \
  value "%s" could not be parsed as a valid assertion value because there is  \
  conflicting  value "%d" for MM(Month) specification
MILD_WARN_ATTR_DUPLICATE_YEAR_ASSERTION_FORMAT_302=The provided \
  value "%s" could not be parsed as a valid assertion value because there is  \
  conflicting  value "%d" for YYYY(Year) specification
MILD_WARN_ATTR_MISSING_YEAR_PARTIAL_TIME_ASSERTION_FORMAT_303=The provided \
 value "%s" could not be parsed as a valid assertion value because it does \
 not contain year in YYYY format
MILD_WARN_ATTR_CONFLICTING_ASSERTION_FORMAT_304=The provided \
 value "%s" could not be parsed as a valid assertion value because more than  \
 one time units are not allowed
opends/src/server/org/opends/server/schema/SchemaConstants.java
@@ -22,7 +22,7 @@
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2008 Sun Microsystems, Inc.
 *      Copyright 2006-2009 Sun Microsystems, Inc.
 */
package org.opends.server.schema;
@@ -1906,5 +1906,89 @@
   * The OID for the "top" objectclass.
   */
  public static final String TOP_OBJECTCLASS_OID = "2.5.6.0";
  /**
   * The name for the relative time greater-than extensible ordering matching
   * rule.
   */
  public static final String EXT_OMR_RELATIVE_TIME_GT_NAME =
          "relativeTimeGTOrderingMatch";
  /**
   * The alternative name for the relative time greater-than extensible
   * ordering matching rule.
   */
  public static final String EXT_OMR_RELATIVE_TIME_GT_ALT_NAME =
          "relativeTimeOrderingMatch.gt";
  /**
   * The OID for the relative time greater-than extensible ordering matching
   * rule.
   */
  public static final String EXT_OMR_RELATIVE_TIME_GT_OID =
          "1.3.6.1.4.1.26027.1.4.5";
  /**
   * The name for the relative time less-than  extensible ordering matching
   * rule.
   */
  public static final String EXT_OMR_RELATIVE_TIME_LT_NAME =
          "relativeTimeLTOrderingMatch";
  /**
   * The alternative name for the relative time less-than extensible ordering
   * matching rule.
   */
  public static final String EXT_OMR_RELATIVE_TIME_LT_ALT_NAME =
          "relativeTimeOrderingMatch.lt";
  /**
   * The OID for the relative time less-than extensible ordering matching rule.
   */
  public static final String EXT_OMR_RELATIVE_TIME_LT_OID =
          "1.3.6.1.4.1.26027.1.4.6";
  /**
   * The OID for the partial date and time extensible matching rule.
   */
  public static final String EXT_PARTIAL_DATE_TIME_OID =
          "1.3.6.1.4.1.26027.1.4.7";
  /**
   * The name for the partial date and time extensible rule.
   */
  public static final String EXT_PARTIAL_DATE_TIME_NAME =
          "partialDateAndTimeMatchingRule";
  /**
   * The preferred index name for partial date and time matching rule.
   */
  public static final String PARTIAL_DATE_TIME_INDEX_NAME = "pdt";
  /**
   * The preferred index name for partial date and time matching rule.
   */
  public static final String RELATIVE_TIME_INDEX_NAME = "rt";
}
opends/src/server/org/opends/server/schema/TimeBasedMatchingRuleFactory.java
New file
@@ -0,0 +1,1366 @@
/*
 * 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
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 */
package org.opends.server.schema;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import org.opends.messages.Message;
import org.opends.server.api.ExtensibleIndexer;
import org.opends.server.api.IndexQueryFactory;
import org.opends.server.api.MatchingRule;
import org.opends.server.api.MatchingRuleFactory;
import org.opends.server.admin.std.server.MatchingRuleCfg;
import org.opends.server.api.AbstractMatchingRule;
import org.opends.server.api.ExtensibleMatchingRule;
import org.opends.server.api.OrderingMatchingRule;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.AttributeValue;
import org.opends.server.types.ByteSequence;
import org.opends.server.types.ByteString;
import org.opends.server.types.ConditionResult;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.IndexConfig;
import org.opends.server.types.InitializationException;
import org.opends.server.types.ResultCode;
import static org.opends.server.util.StaticUtils.*;
import static org.opends.server.util.TimeThread.*;
import static org.opends.messages.SchemaMessages.*;
import static org.opends.server.loggers.ErrorLogger.*;
import static org.opends.server.schema.SchemaConstants.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.schema.GeneralizedTimeSyntax.*;
/**
 * This class acts as a factory for time-based matching rules.
 */
public final class TimeBasedMatchingRuleFactory
        extends MatchingRuleFactory<MatchingRuleCfg>
{
  //Greater-than RelativeTimeMatchingRule.
  private MatchingRule greaterThanRTMRule;
  //Less-than RelativeTimeMatchingRule.
  private MatchingRule lessThanRTMRule;
  //PartialDayAndTimeMatchingRule.
  private MatchingRule partialDTMatchingRule;
  //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 months.
  private static final byte[] JAN = {'j','a','n' };
  private static final byte[] FEB = {'f','e','b'};
  private static final byte[] MAR = {'m','a','r'};
  private static final byte[] APR = {'a','p','r'};
  private static final byte[] MAY = {'m','a','y'};
  private static final byte[] JUN = {'j','u','n'};
  private static final byte[] JUL = {'j','u','l'};
  private static final byte[] AUG = {'a','u','g'};
  private static final byte[] SEP = {'s','e','p'};
  private static final byte[] OCT = {'o','c','t'};
  private static final byte[] NOV = {'n','o','v'};
  private static final byte[] DEC = {'d','e','c'};
  /**
   * {@inheritDoc}
   */
  @Override
  public void initializeMatchingRule(MatchingRuleCfg configuration)
          throws ConfigException, InitializationException
  {
    matchingRules = new HashSet<MatchingRule>();
    greaterThanRTMRule = new RelativeTimeGTOrderingMatchingRule();
    matchingRules.add(greaterThanRTMRule);
    lessThanRTMRule = new RelativeTimeLTOrderingMatchingRule();
    matchingRules.add(lessThanRTMRule);
    partialDTMatchingRule = new PartialDateAndTimeMatchingRule();
    matchingRules.add(partialDTMatchingRule);
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public Collection<MatchingRule> getMatchingRules()
  {
    return Collections.unmodifiableCollection(matchingRules);
  }
  /**
   * This class defines a matching rule which is used for time-based searches.
   */
  private  abstract class TimeBasedMatchingRule extends AbstractMatchingRule
          implements ExtensibleMatchingRule
  {
    /**
     * {@inheritDoc}
     */
    @Override
    public String getDescription()
    {
      //There is no standard definition.
      return null;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public String getSyntaxOID()
    {
       return SYNTAX_GENERALIZED_TIME_OID;
    }
    /**
      * {@inheritDoc}
      */
    @Override
    public ByteString normalizeValue(ByteSequence value)
            throws DirectoryException
    {
      try
      {
        long timestamp = decodeGeneralizedTimeValue(value);
        return ByteString.valueOf(timestamp);
      }
      catch (DirectoryException de)
      {
        switch (DirectoryServer.getSyntaxEnforcementPolicy())
        {
          case REJECT:
            throw de;
          case WARN:
            logError(de.getMessageObject());
            return value.toByteString();
          default:
            return value.toByteString();
        }
      }
    }
  }
 /**
  * This class defines a matching rule which matches  the relative time for
  * time-based searches.
  */
  private abstract class RelativeTimeOrderingMatchingRule
          extends TimeBasedMatchingRule
          implements OrderingMatchingRule
  {
    /**
     * The serial version identifier required to satisfy the compiler because
     * this class implements the <CODE>java.io.Serializable</CODE> interface.
     * This value was generated using the <CODE>serialver</CODE> command-line
     * utility included with the Java SDK.
     */
     private static final long serialVersionUID = -3501812894473163490L;
     /**
      * Indexer associated with this instance.
      */
     protected ExtensibleIndexer indexer;
     /**
      * {@inheritDoc}
      */
    @Override
    public ByteString normalizeAssertionValue(ByteSequence value)
            throws DirectoryException
    {
      /**
      An assertion value may contain one of the following:
      s = second
      m = minute
      h = hour
      d = day
      w = week
      An example assertion is OID:=(-)1d, where a '-' means that the user
      intends to search only the expired events. In this example we are
      searching for an event expired 1 day back.
      Use this method to parse, validate and normalize the assertion value
      into a format to be recognized by the valuesMatch routine. This method
      takes the assertion value, adds/substracts it to/from the current time
      and calculates a time which will be used as a relative time by inherited
       rules.
      */
      int index = 0;
      boolean signed = false;
      byte firstByte = value.byteAt(0);
      if(firstByte == '-')
      {
        //Turn the sign on to go back in past.
        signed = true;
        index = 1;
      }
      else if(firstByte == '+')
      {
        //'+" is not required but we won't reject it either.
        index = 1;
      }
      long second = 0;
      long minute = 0;
      long hour = 0;
      long day = 0;
      long week = 0;
      boolean containsTimeUnit = false;
      long 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;
          }
        }
        else
        {
          Message message = null;
          if(containsTimeUnit)
          {
            //We already have time unit found by now.
            message = WARN_ATTR_CONFLICTING_ASSERTION_FORMAT.
                       get(value.toString());
          }
          else
          {
            switch(value.byteAt(index))
            {
              case 's':
                second = number;
                break;
              case 'm':
                minute = number;
                break;
              case 'h':
                hour = number;
                break;
              case 'd':
                day = number;
                break;
              case 'w':
                week = number;
                break;
              default:
                  message =
                          WARN_ATTR_INVALID_RELATIVE_TIME_ASSERTION_FORMAT.
                          get(value.toString(),(char)value.byteAt(index));
            }
          }
          if(message !=null)
          {
            //Log the message and throw an exception.
            logError(message);
            throw new DirectoryException(
                    ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
          }
          else
          {
            containsTimeUnit = true;
            number = 0;
          }
        }
      }
      if(!containsTimeUnit)
      {
        //There was no time unit so assume it is seconds.
        second = number;
      }
      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);
    }
    /**
     * {@inheritDoc}
     */
    public int compareValues(ByteSequence value1, ByteSequence value2)
    {
      return value1.compareTo(value2);
    }
    /**
      * {@inheritDoc}
      */
    public int compare(byte[] arg0, byte[] arg1)
    {
      return compare(arg0, arg1);
    }
    /**
    * {@inheritDoc}
    */
    public Collection<ExtensibleIndexer> getIndexers(IndexConfig config)
    {
      if(indexer == null)
      {
        indexer = new RelativeTimeExtensibleIndexer(this);
      }
       return Collections.singletonList(indexer);
    }
  }
 /**
  * This class defines a matching rule which calculates the "greater-than"
  * relative time for time-based searches.
  */
  private final class RelativeTimeGTOrderingMatchingRule
          extends RelativeTimeOrderingMatchingRule
  {
    //All the names for this matching rule.
    private final List<String> names;
    /**
     * The serial version identifier required to satisfy the compiler because
     * this class implements the <CODE>java.io.Serializable</CODE> interface.
     * This value was generated using the <CODE>serialver</CODE> command-line
     * utility included with the Java SDK.
     */
     private static final long serialVersionUID = 7247241496402474136L;
    RelativeTimeGTOrderingMatchingRule()
    {
      names = new ArrayList<String>();
      names.add(EXT_OMR_RELATIVE_TIME_GT_NAME);
      names.add(EXT_OMR_RELATIVE_TIME_GT_ALT_NAME);
    }
     /**
      * {@inheritDoc}
      */
    @Override
    public String getName()
    {
      return EXT_OMR_RELATIVE_TIME_GT_NAME;
    }
    /**
      * {@inheritDoc}
      */
    @Override
    public Collection<String> getAllNames()
    {
      return Collections.unmodifiableList(names);
    }
    /**
      * {@inheritDoc}
      */
    @Override
    public String getOID()
    {
      return EXT_OMR_RELATIVE_TIME_GT_OID;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public ConditionResult valuesMatch(ByteSequence attributeValue,
        ByteSequence assertionValue)
    {
      int ret = compareValues(attributeValue, assertionValue);
      if (ret > 0)
      {
        return ConditionResult.TRUE;
      }
      else
      {
        return ConditionResult.FALSE;
      }
    }
    /**
    * {@inheritDoc}
    */
    public <T> T createIndexQuery(ByteSequence assertionValue,
        IndexQueryFactory<T> factory) throws DirectoryException
    {
      return factory.createRangeMatchQuery(indexer
          .getExtensibleIndexID(), normalizeAssertionValue(assertionValue),
          ByteString.empty(), false, false);
    }
  }
  /**
  * This class defines a matching rule which calculates the "less-than"
  * relative time for time-based searches.
  */
  private final class RelativeTimeLTOrderingMatchingRule
          extends RelativeTimeOrderingMatchingRule
  {
    //All the names for this matching rule.
    private final List<String> names;
    /**
     * The serial version identifier required to satisfy the compiler because
     * this class implements the <CODE>java.io.Serializable</CODE> interface.
     * This value was generated using the <CODE>serialver</CODE> command-line
     * utility included with the Java SDK.
     */
   private static final long serialVersionUID = -5122459830973558441L;
    RelativeTimeLTOrderingMatchingRule()
    {
      names = new ArrayList<String>();
      names.add(EXT_OMR_RELATIVE_TIME_LT_NAME);
      names.add(EXT_OMR_RELATIVE_TIME_LT_ALT_NAME);
    }
     /**
      * {@inheritDoc}
      */
    @Override
    public String getName()
    {
      return EXT_OMR_RELATIVE_TIME_LT_NAME;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<String> getAllNames()
    {
      return Collections.unmodifiableList(names);
    }
    /**
      * {@inheritDoc}
      */
    @Override
    public String getOID()
    {
      return EXT_OMR_RELATIVE_TIME_LT_OID;
    }
     /**
     * {@inheritDoc}
     */
    @Override
    public ConditionResult valuesMatch(ByteSequence attributeValue,
        ByteSequence assertionValue)
    {
      int ret = compareValues(attributeValue, assertionValue);
      if (ret < 0)
      {
        return ConditionResult.TRUE;
      }
      else
      {
        return ConditionResult.FALSE;
      }
    }
    /**
    * {@inheritDoc}
    */
    public <T> T createIndexQuery(ByteSequence assertionValue,
        IndexQueryFactory<T> factory) throws DirectoryException
    {
      return factory.createRangeMatchQuery(indexer
          .getExtensibleIndexID(), ByteString.empty(),
          normalizeAssertionValue(assertionValue),false, false);
    }
  }
  /**
   * Extensible Indexer class for Relative Time Matching rules which share
   * the same index. This Indexer is shared by both greater than and less than
   * Relative Time Matching Rules.
   */
  private final class RelativeTimeExtensibleIndexer extends
      ExtensibleIndexer
  {
    /**
     * The Extensible Matching Rule.
     */
    private final RelativeTimeOrderingMatchingRule matchingRule;
    /**
     * Creates a new instance of RelativeTimeExtensibleIndexer.
     *
     * @param matchingRule The relative time Matching Rule.
     */
    private RelativeTimeExtensibleIndexer(
        RelativeTimeOrderingMatchingRule matchingRule)
    {
      this.matchingRule = matchingRule;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public String getExtensibleIndexID()
    {
      return EXTENSIBLE_INDEXER_ID_DEFAULT;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public final void getKeys(AttributeValue value, Set<byte[]> keys)
    {
      ByteString key;
      try
      {
        key = matchingRule.normalizeValue(value.getValue());
        keys.add(key.toByteArray());
      }
      catch (DirectoryException de)
      {
        //don't do anything.
      }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public final void getKeys(AttributeValue value,
        Map<byte[], Boolean> modifiedKeys, Boolean insert)
    {
      Set<byte[]> keys = new HashSet<byte[]>();
      getKeys(value, keys);
      for (byte[] key : keys)
      {
        Boolean cInsert = modifiedKeys.get(key);
        if (cInsert == null)
        {
          modifiedKeys.put(key, insert);
        }
        else if (!cInsert.equals(insert))
        {
          modifiedKeys.remove(key);
        }
      }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public String getPreferredIndexName()
    {
      return RELATIVE_TIME_INDEX_NAME;
    }
  }
  /**
   * This class performs the partial date and time matching capabilities.
   */
  private final class PartialDateAndTimeMatchingRule
          extends TimeBasedMatchingRule
          implements ExtensibleMatchingRule
  {
     /**
      * Indexer associated with this instance.
      */
     private ExtensibleIndexer indexer;
    /**
     * {@inheritDoc}
     */
    @Override
    public String getName()
    {
      return EXT_PARTIAL_DATE_TIME_NAME;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public String getOID()
    {
      return EXT_PARTIAL_DATE_TIME_OID;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<String> getAllNames()
    {
      return Collections.singleton(getName());
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public ByteString normalizeAssertionValue(ByteSequence value)
        throws DirectoryException
    {
     /**
      An assertion value may contain one or all of the following:
      DD = day
      MM = month
      YYYY = year
      An example assertion is OID:=04MM. In this example we are
      searching for entries corresponding to month of april.
      Use this method to parse, validate and normalize the assertion value
      into a format to be recognized by the compare routine. The normalized
      value is actually the format of : DDMMYYYY.
      */
      int date = 0;
      int year = 0;
      int number = 0;
      int month = -1;
      int length = value.length();
      for(int index=0; index<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;
          }
        }
        else
        {
          Message message = null;
          switch(value.byteAt(index))
          {
            case 'D':
              if(!(index < length-1) || value.byteAt(index+1) !='D')
              {
                //the acceptable format is 'DD'.
                message =
                        WARN_ATTR_MISSING_CHAR_PARTIAL_TIME_ASSERTION_FORMAT.
                        get(value.toString(),
                        (char)value.byteAt(index),'D',index+1);
              }
              else if(number == 0)
              {
                message =
                        WARN_ATTR_INVALID_DATE_ASSERTION_FORMAT.get(
                        value.toString(), number);
              }
              else if(date > 0)
              {
                message =
                        WARN_ATTR_DUPLICATE_DATE_ASSERTION_FORMAT.get(
                        value.toString(),date);
              }
              else
              {
                date = number;
                index++;
              }
              break;
            case 'M':
              if(!(index < length-1) || value.byteAt(index+1)!='M')
              {
                //the acceptable value is 'MM'.
                message =
                        WARN_ATTR_MISSING_CHAR_PARTIAL_TIME_ASSERTION_FORMAT.
                        get(value.toString(),
                        (char)value.byteAt(index),'M',index+1);
              }
              else if(number == 0)
              {
                message =
                        WARN_ATTR_INVALID_MONTH_ASSERTION_FORMAT.
                        get(value.toString(),number);
              }
              else if(month > 0)
              {
                message =
                        WARN_ATTR_DUPLICATE_MONTH_ASSERTION_FORMAT.get(
                        value.toString(),month);
              }
              else
              {
                month = number;
                index++;
              }
              break;
            case 'Y':
              if(!(index < length-3))
              {
                //the acceptable value is 'YYYY".
                message =
                        WARN_ATTR_MISSING_YEAR_PARTIAL_TIME_ASSERTION_FORMAT.
                        get(value.toString());
              }
              else
              {
yearLoop: for(int i=index;i<index+3;i++)
                {
                  if(value.byteAt(i) !='Y')
                  {
                    message =
                         WARN_ATTR_MISSING_CHAR_PARTIAL_TIME_ASSERTION_FORMAT.
                        get(value.toString(),(char)value.byteAt(i),'Y',i);
                    break yearLoop;
                  }
                }
                if(message == null)
                {
                  if(number == 0)
                  {
                    message =
                            WARN_ATTR_INVALID_YEAR_ASSERTION_FORMAT.
                            get(value.toString(),number);
                  }
                  else if(year >0)
                  {
                    message = WARN_ATTR_DUPLICATE_YEAR_ASSERTION_FORMAT.
                            get(value.toString(),year);
                  }
                  else
                  {
                    year = number;
                    index+=3;
                  }
                }
              }
              break;
            default:
                message =
                        WARN_ATTR_INVALID_PARTIAL_TIME_ASSERTION_FORMAT.
                        get(value.toString(),(char)value.byteAt(index));
          }
          if(message !=null)
          {
            logError(message);
            throw new DirectoryException(
                    ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
          }
          else
          {
            number = 0;
          }
        }
      }
      //Validate year, month and date in that order.
      if(year < 0)
      {
        //A future date is allowed.
        Message message =
                WARN_ATTR_INVALID_YEAR_ASSERTION_FORMAT.
                get(value.toString(),year);
        logError(message);
        throw new DirectoryException(
                ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
      }
      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:
          Message message =
                WARN_ATTR_INVALID_MONTH_ASSERTION_FORMAT.
                get(value.toString(),month);
          logError(message);
           throw new DirectoryException(
                   ResultCode.INVALID_ATTRIBUTE_SYNTAX, 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)
      {
        Message message =
                WARN_ATTR_INVALID_DATE_ASSERTION_FORMAT.
                get(value.toString(),date);
        logError(message);
        throw new DirectoryException(
                ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
      }
      /**
       * Since we reached here we have a valid assertion value. Construct
       * a normalized value in the order: DATE MONTH YEAR.
       */
      ByteBuffer bb = ByteBuffer.allocate(3*4);
      bb.putInt(date);
      bb.putInt(month);
      bb.putInt(year);
      return ByteString.wrap(bb.array());
    }
     /**
     * {@inheritDoc}
     */
    @Override
    public ConditionResult valuesMatch(ByteSequence attributeValue,
        ByteSequence assertionValue)
    {
      long timeInMS = ((ByteString)attributeValue).toLong();
      //Build the information from the attribute value.
      GregorianCalendar cal = new GregorianCalendar(TIME_ZONE_UTC_OBJ);
      cal.setLenient(false);
      cal.setTimeInMillis(timeInMS);
      int date = cal.get(Calendar.DATE);
      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 assertDate = bb.getInt(0);
      int assertMonth = bb.getInt(4);
      int assertYear = bb.getInt(8);
      //All the non-zero values should match.
      if(assertDate !=0 && assertDate != date)
      {
        return ConditionResult.FALSE;
      }
      if(assertMonth !=-1 && assertMonth != month)
      {
        return ConditionResult.FALSE;
      }
      if(assertYear !=0 && assertYear != year)
      {
        return ConditionResult.FALSE;
      }
     return ConditionResult.TRUE;
    }
    /**
      * {@inheritDoc}
      */
    public Collection<ExtensibleIndexer> getIndexers(IndexConfig config)
    {
      if(indexer == null)
      {
        indexer = new PartialDateAndTimeExtensibleIndexer(this);
      }
      return Collections.singletonList(indexer);
    }
    /**
      * {@inheritDoc}
      */
    public <T> T createIndexQuery(ByteSequence assertionValue,
            IndexQueryFactory<T> factory) throws DirectoryException
    {
      //Build the information from the assertion value.
      byte[] arr = normalizeAssertionValue(assertionValue).toByteArray();
      ByteBuffer bb = ByteBuffer.wrap(arr);
      int assertDate = bb.getInt(0);
      int assertMonth = bb.getInt(4);
      int assertYear = bb.getInt(8);
      List<T> queries = new ArrayList<T>();
      if(assertDate >0)
      {
        queries.add(factory.createExactMatchQuery(
                indexer.getExtensibleIndexID(),
                ByteString.valueOf(assertDate)));
      }
      if(assertMonth >=0)
      {
        queries.add(factory.createExactMatchQuery(
                indexer.getExtensibleIndexID(),
                ByteString.wrap(getMonthKey(assertMonth))));
      }
      if(assertYear > 0)
      {
        queries.add(factory.createExactMatchQuery(
                indexer.getExtensibleIndexID(),
                ByteString.valueOf(assertYear)));
      }
      return factory.createIntersectionQuery(queries);
    }
    /**
     * Decomposes an attribute value into a set of partial date and time index
     * keys.
     *
     * @param attValue
     *          The normalized attribute value
     * @param set
     *          A set into which the keys will be inserted.
     */
    private void timeKeys(ByteString attributeValue, Set<byte[]> keys)
    {
      long timeInMS = 0L;
      try
      {
        timeInMS = decodeGeneralizedTimeValue(attributeValue);
      }
      catch(DirectoryException de)
      {
        //If the schema check is on this should never reach here. If not then we
        //would return from here.
        return;
      }
      //Build the information from the attribute value.
      GregorianCalendar cal = new GregorianCalendar(TIME_ZONE_UTC_OBJ);
      cal.setTimeInMillis(timeInMS);
      int date = cal.get(Calendar.DATE);
      int month = cal.get(Calendar.MONTH);
      int year = cal.get(Calendar.YEAR);
      //Insert date.
      if(date > 0)
      {
        keys.add(ByteString.valueOf(date).toByteArray());
      }
      //Insert month.
      if(month >=0)
      {
        keys.add(getMonthKey(month));
      }
      if(year > 0)
      {
        keys.add(ByteString.valueOf(year).toByteArray());
      }
    }
    //Returns a byte array of for the corresponding month.
    private byte[] getMonthKey(int month)
    {
      byte[] key = null;
      switch(month)
      {
        case Calendar.JANUARY:
          key = JAN;
          break;
        case Calendar.FEBRUARY:
          key = FEB;
          break;
        case Calendar.MARCH:
          key = MAR;
          break;
        case Calendar.APRIL:
          key = APR;
          break;
        case Calendar.MAY:
          key = MAY;
          break;
        case Calendar.JUNE:
          key = JUN;
          break;
        case Calendar.JULY:
          key = JUL;
          break;
        case Calendar.AUGUST:
          key = AUG;
          break;
        case Calendar.SEPTEMBER:
          key = SEP;
          break;
        case Calendar.OCTOBER:
          key = OCT;
          break;
        case Calendar.NOVEMBER:
          key = NOV;
          break;
        case Calendar.DECEMBER:
          key = DEC;
          break;
        default:
          key = new byte[0];
       }
      return key;
    }
  }
   /**
   * Extensible Indexer class for Partial Date and Time Matching rules.
   */
  private final class PartialDateAndTimeExtensibleIndexer extends
      ExtensibleIndexer
  {
    // The partial date and Time matching Rule.
    private final PartialDateAndTimeMatchingRule matchingRule;
    /**
     * Creates a new instance of PartialDateAndTimeExtensibleIndexer.
     *
     * @param matchingRule
     *          The PartialDateAndTime Rule.
     */
    private PartialDateAndTimeExtensibleIndexer(
        PartialDateAndTimeMatchingRule matchingRule)
    {
      this.matchingRule = matchingRule;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void getKeys(AttributeValue value, Set<byte[]> keys)
    {
      matchingRule.timeKeys(value.getValue(), keys);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void getKeys(AttributeValue attValue,
        Map<byte[], Boolean> modifiedKeys, Boolean insert)
    {
      Set<byte[]> keys = new HashSet<byte[]>();
      getKeys(attValue, keys);
      for (byte[] key : keys)
      {
        Boolean cInsert = modifiedKeys.get(key);
        if (cInsert == null)
        {
          modifiedKeys.put(key, insert);
        }
        else if (!cInsert.equals(insert))
        {
          modifiedKeys.remove(key);
        }
      }
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public String getPreferredIndexName()
    {
      return PARTIAL_DATE_TIME_INDEX_NAME;
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public String getExtensibleIndexID()
    {
      return EXTENSIBLE_INDEXER_ID_DEFAULT;
    }
  }
}
opends/src/server/org/opends/server/util/ServerConstants.java
@@ -2954,6 +2954,14 @@
  /**
   * The extensible indexer identifier string that will be used for default
   * type.
   */
  public static final String EXTENSIBLE_INDEXER_ID_DEFAULT="ext";
  /**
   * The lines that make up the CDDL header.  They will not have any prefix, so
   * an appropriate prefix may need to be added for some cases (e.g., "# " for
   * shell scripts, "rem " for batch files, etc.).
opends/tests/unit-tests-testng/src/server/org/opends/server/schema/TimeBasedMatchingRuleTest.java
New file
@@ -0,0 +1,508 @@
/*
 * 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
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * 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
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 */
package org.opends.server.schema;
import java.util.Calendar;
import java.util.GregorianCalendar;
import static org.testng.Assert.*;
import java.util.List;
import java.util.TimeZone;
import org.opends.server.TestCaseUtils;
import org.opends.server.api.MatchingRule;
import org.opends.server.core.DirectoryServer;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.protocols.ldap.LDAPFilter;
import org.opends.server.types.ByteString;
import org.opends.server.types.DN;
import org.opends.server.types.DereferencePolicy;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchResultEntry;
import org.opends.server.types.SearchScope;
import org.opends.server.util.TimeThread;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.opends.server.schema.GeneralizedTimeSyntax.*;
import static org.opends.server.schema.SchemaConstants.*;
/**
 * This class tests various time-based matching rules.
 */
public final class TimeBasedMatchingRuleTest
        extends SchemaTestCase
{
  //User DNs to be used in tests.
  private DN user1;
  private DN user2 ;
  private DN user3;
  private DN user4;
  private DN user5;
 private final static String TIME_ATTR = "test-time-attribute";
 GregorianCalendar cal = null;
  /**
   * Ensures that the Directory Server is running before executing the
   * testcases.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  {
    TestCaseUtils.startServer();
    TestCaseUtils.initializeTestBackend(true);
    user1 = DN.decode("cn=user1,dc=example,dc=com");
    user2 = DN.decode("cn=user2,dc=example,dc=com");
    user3 = DN.decode("cn=user3,dc=example,dc=com");
    user4 = DN.decode("cn=user4,dc=example,dc=com");
    user5 = DN.decode("cn=user5,dc=example,dc=com");
    /**
    Extend the schema and add an attribute which is baseed on
    generalizedTimeSyntax. Since all the existing attributes based
    on that syntax are read-only, let us create a new attribute and
    add it.*/
   int resultCode = TestCaseUtils.applyModifications(true,
    "dn: cn=schema",
    "changetype: modify",
    "add: attributeTypes",
    "attributeTypes: ( test-time-oid NAME 'test-time-attribute' DESC 'Test time attribute'  EQUALITY   " +
    "generalizedTimeMatch ORDERING  generalizedTimeOrderingMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE )",
    "-",
    "add: objectclasses",
    "objectclasses: ( oc-oid NAME 'testOC' SUP top AUXILIARY MUST test-time-attribute)"
    );
    assertTrue(resultCode == 0);
        //Get the current time.
   cal = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
   cal.setLenient(false);
  }
  /**
   * Test to search using the less-than relative time matching rule for expired events.
   */
  @Test()
  public void testRTLessThanExpiredEvents() throws Exception
  {
    try
    {
      populateEntries();
      InternalClientConnection conn =
           InternalClientConnection.getRootConnection();
      InternalSearchOperation searchOperation =
           new InternalSearchOperation(
                conn,
                InternalClientConnection.nextOperationID(),
                InternalClientConnection.nextMessageID(),
                null,
                ByteString.valueOf("dc=example,dc=com"),
                SearchScope.WHOLE_SUBTREE,
                DereferencePolicy.NEVER_DEREF_ALIASES,
                Integer.MAX_VALUE,
                Integer.MAX_VALUE,
                false,
                LDAPFilter.decode(TIME_ATTR+":"+EXT_OMR_RELATIVE_TIME_LT_OID+":=-60m"), //
                null, null);
      searchOperation.run();
      assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS);
      List<SearchResultEntry> entries = searchOperation.getSearchEntries();
      assertTrue(dnFoundInEntryList(entries,user1,user2));
    }
    finally
    {
      TestCaseUtils.clearJEBackend(false, "userRoot", "dc=example,dc=com");
    }
  }
  /**
   * Test to search using the less-than relative time matching rule for future events.
   */
  @Test()
  public void testRTLessThanFutureEvents() throws Exception
  {
    try
    {
      populateEntries();
      InternalClientConnection conn =
           InternalClientConnection.getRootConnection();
      InternalSearchOperation searchOperation =
           new InternalSearchOperation(
                conn,
                InternalClientConnection.nextOperationID(),
                InternalClientConnection.nextMessageID(),
                null,
                ByteString.valueOf("dc=example,dc=com"),
                SearchScope.WHOLE_SUBTREE,
                DereferencePolicy.NEVER_DEREF_ALIASES,
                Integer.MAX_VALUE,
                Integer.MAX_VALUE,
                false,
                LDAPFilter.decode(TIME_ATTR+":"+EXT_OMR_RELATIVE_TIME_LT_OID+":=1d"),
                null, null);
      searchOperation.run();
      assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS);
      List<SearchResultEntry> entries = searchOperation.getSearchEntries();
      assertTrue(entries.size() == 4 && dnFoundInEntryList(entries,user1,user2,user3,user5));
    }
    finally
    {
      TestCaseUtils.clearJEBackend(false, "userRoot", "dc=example,dc=com");
    }
  }
    /**
   * Test to search using the greater-than relative time matching rule for expired events.
   */
  @Test()
  public void testRTGreaterThanExpiredEvents() throws Exception
  {
    try
    {
      populateEntries();
      InternalClientConnection conn =
           InternalClientConnection.getRootConnection();
      InternalSearchOperation searchOperation =
           new InternalSearchOperation(
                conn,
                InternalClientConnection.nextOperationID(),
                InternalClientConnection.nextMessageID(),
                null,
                ByteString.valueOf("dc=example,dc=com"),
                SearchScope.WHOLE_SUBTREE,
                DereferencePolicy.NEVER_DEREF_ALIASES,
                Integer.MAX_VALUE,
                Integer.MAX_VALUE,
                false,
                LDAPFilter.decode(TIME_ATTR+":"+EXT_OMR_RELATIVE_TIME_GT_OID+":=-1h"),
                null, null);
      searchOperation.run();
      assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS);
      List<SearchResultEntry> entries = searchOperation.getSearchEntries();
      assertTrue(entries.size()==3 && dnFoundInEntryList(entries,user3,user4,user5));
    }
    finally
    {
      TestCaseUtils.clearJEBackend(false, "userRoot", "dc=example,dc=com");
    }
  }
    /**
   * Test to search using the greater-than relative time matching rule for future events.
   */
  @Test()
  public void testRTGreaterThanFutureEvents() throws Exception
  {
    try
    {
      populateEntries();
      InternalClientConnection conn =
           InternalClientConnection.getRootConnection();
      InternalSearchOperation searchOperation =
           new InternalSearchOperation(
                conn,
                InternalClientConnection.nextOperationID(),
                InternalClientConnection.nextMessageID(),
                null,
                ByteString.valueOf("dc=example,dc=com"),
                SearchScope.WHOLE_SUBTREE,
                DereferencePolicy.NEVER_DEREF_ALIASES,
                Integer.MAX_VALUE,
                Integer.MAX_VALUE,
                false,
                LDAPFilter.decode(TIME_ATTR+":"+EXT_OMR_RELATIVE_TIME_GT_OID+":=0s"),
                null, null);
      searchOperation.run();
      assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS);
      List<SearchResultEntry> entries = searchOperation.getSearchEntries();
      assertTrue(entries.size()==2 && dnFoundInEntryList(entries,user3,user4));
    }
    finally
    {
      TestCaseUtils.clearJEBackend(false, "userRoot", "dc=example,dc=com");
    }
  }
   /**
   * Test to search using the partial date and time matching rule for an assertion value.
   */
  @Test()
  public void testPartialDateNTimeMatchingRule() throws Exception
  {
    try
    {
      populateEntries();
      InternalClientConnection conn =
           InternalClientConnection.getRootConnection();
      int month = cal.get(Calendar.MONTH)+1; //month starts from 0 in the Calendar.
      String assertion = cal.get(Calendar.DATE)+"DD"+month+"MM";
      InternalSearchOperation searchOperation =
           new InternalSearchOperation(
                conn,
                InternalClientConnection.nextOperationID(),
                InternalClientConnection.nextMessageID(),
                null,
                ByteString.valueOf("dc=example,dc=com"),
                SearchScope.WHOLE_SUBTREE,
                DereferencePolicy.NEVER_DEREF_ALIASES,
                Integer.MAX_VALUE,
                Integer.MAX_VALUE,
                false,
                LDAPFilter.decode(TIME_ATTR+":"+EXT_PARTIAL_DATE_TIME_OID+":="+assertion),
                null,null);
      searchOperation.run();
      assertEquals(searchOperation.getResultCode(), ResultCode.SUCCESS);
      List<SearchResultEntry> entries = searchOperation.getSearchEntries();
      assertTrue(entries.size()==3 && dnFoundInEntryList(entries,user1,user3,user5));
    }
    finally
    {
      TestCaseUtils.clearJEBackend(false, "userRoot", "dc=example,dc=com");
    }
  }
  /**
   * Tests the assertion syntax of the relative time matching rules.
   */
  @Test(dataProvider= "relativeTimeValues")
  public void testRelativeTimeMatchingRuleAssertionSyntax(String assertion,boolean isValid)
  {
    MatchingRule relativeTimeLTRule =
            DirectoryServer.getOrderingMatchingRule(
            EXT_OMR_RELATIVE_TIME_LT_ALT_NAME.toLowerCase());
    boolean exception = false;
    try
    {
      relativeTimeLTRule.normalizeAssertionValue(ByteString.valueOf(assertion));
    }
    catch(DirectoryException e)
    {
      //invalid values will throw an exception.
      exception = true;
      assertTrue(!isValid);
    }
    if(!isValid)
    {
      //An invalid value can't get away without throwing exception.
      assertTrue(exception);
    }
  }
  /**
   * Tests the assertion syntax of the partial date and time matching rules.
   */
  @Test(dataProvider= "partialDateTimeValues")
  public void testPartialDateTimeMatchingRuleAssertionSyntax(String assertion,boolean isValid)
  {
    MatchingRule partialDTRule =
            DirectoryServer.getMatchingRule(EXT_PARTIAL_DATE_TIME_OID);
    boolean exception = false;
    try
    {
      partialDTRule.normalizeAssertionValue(ByteString.valueOf(assertion));
    }
    catch(DirectoryException e)
    {
      //invalid values will throw an exception.
      exception = true;
      assertTrue(!isValid);
    }
    if(!isValid)
    {
      assertTrue(exception);
    }
  }
  /**
   * Generates data for testing relative time matching rule assertion syntax.
   */
  @DataProvider(name="relativeTimeValues")
  private Object[][] createRelativeTimeValues()
  {
    return new Object[][] {
      {"1s",true},
      {"1s0d",false},
      {"-1d",true},
      {"2h",true},
      {"+2w",true},
      {"0",true},
      {"0s",true},
      {"0d",true},
      {"xyz",false},
      {"12w-2d",false},
      {"1s2s",false},
      {"1d4s5d",false}
    };
  }
  /**
   * Generates data for testing partial date and time assertion syntax.
   */
  @DataProvider(name="partialDateTimeValues")
  private Object[][] createPartialDateTimeValues()
  {
    //Get the date today.
    int date = cal.get(Calendar.DATE);
    int month = cal.get(Calendar.MONTH);
    int year = cal.get(Calendar.YEAR);
    return new Object[][] {
      {"20MM30DD1978YY",false},
      {"02MM29DD2009YY",false},
      {"02MM31DD2010YY",false},
      {"02MM29DD2008YYYY",true},
      {"DDYY",false},
      {"02DD",true},
      {"12MM",true},
      {"1978YYYY",true},
      {"0MM",false},
      {"20MM03DD10MM",false},
      {date+"DD",true},
      {month+"MM",true},
      {year+"YYYY",true},
      {month+"MM"+date+"DD",true},
      {year+"YYYY"+date+"DD",true},
      {month+"MM"+year+"YYYY"+date+"DD",true}
    };
  }
//validate if the args are found in the entries list.
  private boolean dnFoundInEntryList( List<SearchResultEntry> entries,DN ... dns)
  {
    for(DN dn: dns)
    {
      boolean found = false;
      for(SearchResultEntry entry: entries)
      {
        System.out.println("dn from the current entry is " + entry.getDN());
        if(entry.getDN().equals(dn))
        {
          found = true;
        }
      }
      if(!found)
      {
        return false;
      }
    }
    return true;
  }
  //Creates the entries.
  private void populateEntries() throws Exception
  {
    //Get the current time from the TimeThread. Using the current time from new
    // calendar may fail if the time thread using a stale time.
    long currentTime = TimeThread.getTime();
    TestCaseUtils.clearJEBackend(true, "userRoot", "dc=example,dc=com");
    TestCaseUtils.addEntries(
      "dn: cn=user1,dc=example,dc=com",
      "objectclass: person",
      "objectclass: testoc",
      "cn: user1",
      "sn: user1",
      "test-time-attribute: "+ format(currentTime-4000*1000), //more than 1 hour old.
      "",
      "dn: cn=user2,dc=example,dc=com",
      "objectclass: person",
      "objectclass: testoc",
      "cn: user2",
      "sn: user2",
      "test-time-attribute: " + format(currentTime-25*3600*1000), //more than  a day old.
      "",
      "dn: cn=user3,dc=example,dc=com",
      "objectclass: person",
      "objectclass: testoc",
      "cn: user3",
      "sn: user3",
      "test-time-attribute: " + format(currentTime+4000*1000),  //more than 1 hour in advance.
      "",
      "dn: cn=user4,dc=example,dc=com",
      "objectclass: person",
      "objectclass: testoc",
      "cn: user4",
      "sn: user4",
      "test-time-attribute: " + format(currentTime+25*3600*1000),  // more than 1 day in advance
      "",
      "dn: cn=user5,dc=example,dc=com",
      "objectclass: person",
      "objectclass: testoc",
      "cn: user4",
      "sn: user4",
      "test-time-attribute: " + format(currentTime) // now.
    );
  }
}