From ed270f32bf132018992ab8a9c5978f7dbf33f263 Mon Sep 17 00:00:00 2001
From: sin <sin@localhost>
Date: Thu, 02 Jul 2009 17:40:53 +0000
Subject: [PATCH] Issue 4075 : Provide implementations for the relative and partial time matching rules

---
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/TimeBasedMatchingRuleTest.java |  508 ++++++++++++++
 opendj-sdk/opends/resource/config/config.ldif                                                                |    8 
 opendj-sdk/opends/src/messages/messages/schema.properties                                                    |   36 +
 opendj-sdk/opends/src/server/org/opends/server/schema/TimeBasedMatchingRuleFactory.java                      | 1366 ++++++++++++++++++++++++++++++++++++++++
 opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java                                     |    8 
 opendj-sdk/opends/src/server/org/opends/server/schema/SchemaConstants.java                                   |   86 ++
 opendj-sdk/opends/resource/schema/02-config.ldif                                                             |    5 
 7 files changed, 2,016 insertions(+), 1 deletions(-)

diff --git a/opendj-sdk/opends/resource/config/config.ldif b/opendj-sdk/opends/resource/config/config.ldif
index aea468a..0e486b7 100644
--- a/opendj-sdk/opends/resource/config/config.ldif
+++ b/opendj-sdk/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
diff --git a/opendj-sdk/opends/resource/schema/02-config.ldif b/opendj-sdk/opends/resource/schema/02-config.ldif
index cdea6f0..6aba8b1 100644
--- a/opendj-sdk/opends/resource/schema/02-config.ldif
+++ b/opendj-sdk/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' )
 
diff --git a/opendj-sdk/opends/src/messages/messages/schema.properties b/opendj-sdk/opends/src/messages/messages/schema.properties
index 7f2e681..bf4c5a7 100644
--- a/opendj-sdk/opends/src/messages/messages/schema.properties
+++ b/opendj-sdk/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
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/schema/SchemaConstants.java b/opendj-sdk/opends/src/server/org/opends/server/schema/SchemaConstants.java
index 348092f..9e51258 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/schema/SchemaConstants.java
+++ b/opendj-sdk/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";
+
 }
 
diff --git a/opendj-sdk/opends/src/server/org/opends/server/schema/TimeBasedMatchingRuleFactory.java b/opendj-sdk/opends/src/server/org/opends/server/schema/TimeBasedMatchingRuleFactory.java
new file mode 100644
index 0000000..70d5486
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/schema/TimeBasedMatchingRuleFactory.java
@@ -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;
+    }
+  }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java b/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java
index 7230cb6..045ec23 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/util/ServerConstants.java
+++ b/opendj-sdk/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.).
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/TimeBasedMatchingRuleTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/TimeBasedMatchingRuleTest.java
new file mode 100644
index 0000000..9d32ae5
--- /dev/null
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/schema/TimeBasedMatchingRuleTest.java
@@ -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.
+    );
+  }
+}

--
Gitblit v1.10.0