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

Nicolas Capponi
06.50.2014 4cb8262e95fde77e6a0d2c84f1aa118e3b1ee850
opendj3-server-dev/src/server/org/opends/server/schema/TimeBasedMatchingRuleFactory.java
@@ -26,47 +26,17 @@
 */
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.Set;
import java.util.TimeZone;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.forgerock.opendj.config.server.ConfigException;
import org.forgerock.opendj.ldap.Assertion;
import org.forgerock.opendj.ldap.ByteSequence;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ByteStringBuilder;
import org.forgerock.opendj.ldap.ConditionResult;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.opendj.ldap.spi.IndexQueryFactory;
import org.forgerock.opendj.ldap.spi.IndexingOptions;
import org.forgerock.opendj.ldap.schema.CoreSchema;
import org.forgerock.opendj.ldap.schema.MatchingRule;
import org.opends.server.admin.std.server.MatchingRuleCfg;
import org.opends.server.api.AbstractMatchingRule;
import org.opends.server.api.ExtensibleIndexer;
import org.opends.server.api.ExtensibleMatchingRule;
import org.opends.server.api.MatchingRule;
import org.opends.server.api.MatchingRuleFactory;
import org.opends.server.api.OrderingMatchingRule;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.InitializationException;
import org.opends.server.util.StaticUtils;
import static org.opends.messages.SchemaMessages.*;
import static org.opends.server.schema.GeneralizedTimeSyntax.*;
import static org.opends.server.schema.SchemaConstants.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
import static org.opends.server.util.TimeThread.*;
/**
 * This class acts as a factory for time-based matching rules.
@@ -75,49 +45,23 @@
        extends MatchingRuleFactory<MatchingRuleCfg>
{
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
  /** 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 generating keys. */
  private static final char SECOND = 's';
  private static final char MINUTE = 'm';
  private static final char HOUR = 'h';
  private static final char MONTH = 'M';
  private static final char DATE = 'D';
  private static final char YEAR = 'Y';
  /** {@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);
    // relative time greater than
    matchingRules.add(CoreSchema.getInstance().getMatchingRule("1.3.6.1.4.1.26027.1.4.5"));
    // relative time less than
    matchingRules.add(CoreSchema.getInstance().getMatchingRule("1.3.6.1.4.1.26027.1.4.6"));
    // partial date and time
    matchingRules.add(CoreSchema.getInstance().getMatchingRule("1.3.6.1.4.1.26027.1.4.7"));
  }
  /** {@inheritDoc} */
  @Override
  public Collection<MatchingRule> getMatchingRules()
@@ -125,975 +69,4 @@
    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 normalizeAttributeValue(ByteSequence value)
            throws DecodeException
    {
      try
      {
        long timestamp = decodeGeneralizedTimeValue(value);
        return ByteString.valueOf(timestamp);
      }
      catch (DirectoryException de)
      {
        switch (DirectoryServer.getSyntaxEnforcementPolicy())
        {
          case REJECT:
            throw DecodeException.error(de.getMessageObject(), de);
          case WARN:
            logger.error(de.getMessageObject());
            break;
        }
        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 DecodeException
    {
      /**
      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;
      int number = 0;
      for(; index<value.length(); index++)
      {
        byte b = value.byteAt(index);
        if(isDigit((char)b))
        {
          number = multiplyByTenThenAddUnits(number, b);
        }
        else
        {
          LocalizableMessage message = null;
          if(containsTimeUnit)
          {
            //We already have time unit found by now.
            message = WARN_ATTR_CONFLICTING_ASSERTION_FORMAT.get(value);
          }
          else
          {
            switch(b)
            {
              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, (char) b);
            }
          }
          if(message !=null)
          {
            //Log the message and throw an exception.
            logger.error(message);
            throw DecodeException.error(message);
          }
          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 ByteString.valueOf(signed ? now - delta : now + delta);
    }
    /** {@inheritDoc} */
    @Override
    public int compareValues(ByteSequence value1, ByteSequence value2)
    {
      return value1.compareTo(value2);
    }
    /** {@inheritDoc} */
    @Override
    public int compare(byte[] arg0, byte[] arg1)
    {
      return StaticUtils.compare(arg0, arg1);
    }
    /** {@inheritDoc} */
    @Override
    public Collection<ExtensibleIndexer> getIndexers()
    {
      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 Collection<String> getNames()
    {
      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);
      return ConditionResult.valueOf(ret > 0);
    }
    /** {@inheritDoc} */
    @Override
    public Assertion getAssertion(final ByteSequence value)
        throws DecodeException
    {
      final ByteString assertionValue = normalizeAssertionValue(value);
      return new Assertion()
      {
        @Override
        public ConditionResult matches(ByteSequence attributeValue)
        {
          return valuesMatch(attributeValue, assertionValue);
        }
        @Override
        public <T> T createIndexQuery(IndexQueryFactory<T> factory)
            throws DecodeException
        {
          return factory.createRangeMatchQuery(indexer.getExtensibleIndexID(),
              assertionValue, ByteString.empty(), false, false);
        }
      };
    }
    /** {@inheritDoc} */
    @Override
    public <T> T createIndexQuery(ByteSequence assertionValue,
        IndexQueryFactory<T> factory) throws DecodeException
    {
      return getAssertion(assertionValue).createIndexQuery(factory);
    }
  }
  /**
  * 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 Collection<String> getNames()
    {
      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);
      return ConditionResult.valueOf(ret < 0);
    }
    /** {@inheritDoc} */
    @Override
    public Assertion getAssertion(final ByteSequence value)
        throws DecodeException
    {
      final ByteString assertionValue = normalizeAssertionValue(value);
      return new Assertion()
      {
        @Override
        public ConditionResult matches(ByteSequence attributeValue)
        {
          return valuesMatch(attributeValue, assertionValue);
        }
        @Override
        public <T> T createIndexQuery(IndexQueryFactory<T> factory)
            throws DecodeException
        {
          return factory.createRangeMatchQuery(indexer.getExtensibleIndexID(),
              ByteString.empty(), assertionValue, false, false);
        }
      };
    }
    /** {@inheritDoc} */
    @Override
    public <T> T createIndexQuery(ByteSequence assertionValue,
        IndexQueryFactory<T> factory) throws DecodeException
    {
      return getAssertion(assertionValue).createIndexQuery(factory);
    }
  }
  /**
   * 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 createKeys(Schema schema, ByteSequence value2,
        IndexingOptions options, Collection<ByteString> keys)
        throws DecodeException
    {
      keys.add(matchingRule.normalizeAttributeValue(value2));
    }
    /** {@inheritDoc} */
    @Override
    public String getIndexID()
    {
      return RELATIVE_TIME_INDEX_NAME + "." + getExtensibleIndexID();
    }
  }
  /**
   * This class performs the partial date and time matching capabilities.
   */
  private final class PartialDateAndTimeMatchingRule
          extends TimeBasedMatchingRule
  {
    /**
     * Indexer associated with this instance.
     */
    private ExtensibleIndexer indexer;
    /** {@inheritDoc} */
    @Override
    public String getOID()
    {
      return EXT_PARTIAL_DATE_TIME_OID;
    }
    /** {@inheritDoc} */
    @Override
    public Collection<String> getNames()
    {
      return Collections.singleton(EXT_PARTIAL_DATE_TIME_NAME);
    }
    /** {@inheritDoc} */
    @Override
    public ByteString normalizeAssertionValue(ByteSequence value)
        throws DecodeException
    {
     /**
      An assertion value may contain one or all of the following:
      D = day
      M = month
      Y = year
      h = hour
      m = month
      s = second
      An example assertion is OID:=04M. 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 : smhDMY.
      */
      final int initDate = 0;
      final int initVal = -1;
      int second = initVal;
      int minute = initVal;
      int hour = initVal;
      int date = initDate;
      int month = initVal;
      int year = initDate;
      int number = 0;
      int length = value.length();
      for(int index=0; index<length; index++)
      {
        byte b = value.byteAt(index);
        if(isDigit((char)b))
        {
          number = multiplyByTenThenAddUnits(number, b);
        }
        else
        {
          LocalizableMessage message = null;
          switch(b)
          {
            case 's':
              if (second != initVal)
              {
                 message = WARN_ATTR_DUPLICATE_SECOND_ASSERTION_FORMAT.get(value, date);
              }
              else
              {
                second = number;
              }
              break;
            case 'm':
              if (minute != initVal)
              {
                 message = WARN_ATTR_DUPLICATE_MINUTE_ASSERTION_FORMAT.get(value, date);
              }
              else
              {
                minute = number;
              }
              break;
            case 'h':
              if (hour != initVal)
              {
                 message = WARN_ATTR_DUPLICATE_HOUR_ASSERTION_FORMAT.get(value, date);
              }
              else
              {
                hour = number;
              }
              break;
            case 'D':
              if(number == 0)
              {
                message = WARN_ATTR_INVALID_DATE_ASSERTION_FORMAT.get(value, number);
              }
            else if (date != initDate)
              {
                message = WARN_ATTR_DUPLICATE_DATE_ASSERTION_FORMAT.get(value, date);
              }
              else
              {
                date = number;
              }
              break;
            case 'M':
              if (number == 0)
              {
                message = WARN_ATTR_INVALID_MONTH_ASSERTION_FORMAT.get(value, number);
              }
              else if (month != initVal)
              {
                message = WARN_ATTR_DUPLICATE_MONTH_ASSERTION_FORMAT.get(value, month);
              }
              else
              {
                month = number;
              }
              break;
            case 'Y':
              if(number == 0)
              {
                message = WARN_ATTR_INVALID_YEAR_ASSERTION_FORMAT.get(value, number);
              }
              else if (year != initDate)
              {
                message = WARN_ATTR_DUPLICATE_YEAR_ASSERTION_FORMAT.get(value, year);
              }
              else
              {
                year = number;
              }
              break;
            default:
              message = WARN_ATTR_INVALID_PARTIAL_TIME_ASSERTION_FORMAT.get(value, (char) b);
          }
          if(message !=null)
          {
            logger.error(message);
            throw DecodeException.error(message);
          }
          number = 0;
        }
      }
      month = toCalendarMonth(month, value);
      //Validate year, month , date , hour, minute and second in that order.
      // -1 values are allowed when these values have not been provided
      if (year < 0)
      {
        //A future date is allowed.
        logAndThrow(WARN_ATTR_INVALID_YEAR_ASSERTION_FORMAT.get(value, year));
      }
      if (isDateInvalid(date, month, year))
      {
        logAndThrow(WARN_ATTR_INVALID_DATE_ASSERTION_FORMAT.get(value, date));
      }
      if (hour < initVal || hour > 23)
      {
        logAndThrow(WARN_ATTR_INVALID_HOUR_ASSERTION_FORMAT.get(value, hour));
      }
      if (minute < initVal || minute > 59)
      {
        logAndThrow(WARN_ATTR_INVALID_MINUTE_ASSERTION_FORMAT.get(value, minute));
      }
      if (second < initVal || second > 60) // Consider leap seconds.
      {
        logAndThrow(WARN_ATTR_INVALID_SECOND_ASSERTION_FORMAT.get(value, second));
      }
      // Since we reached here we have a valid assertion value.
      // Construct a normalized value in the order: SECOND MINUTE HOUR  DATE MONTH YEAR.
      ByteBuffer bb = ByteBuffer.allocate(6*4);
      bb.putInt(second);
      bb.putInt(minute);
      bb.putInt(hour);
      bb.putInt(date);
      bb.putInt(month);
      bb.putInt(year);
      return ByteString.wrap(bb.array());
    }
    private void logAndThrow(LocalizableMessage message) throws DecodeException
    {
      logger.warn(message);
      throw DecodeException.error(message);
    }
    private boolean isDateInvalid(int date, int month, int year)
    {
      switch (date)
      {
      case 29:
        return month == Calendar.FEBRUARY && !isLeapYear(year);
      case 30:
        return month == Calendar.FEBRUARY;
      case 31:
        return month != -1 && month != Calendar.JANUARY
            && month != Calendar.MARCH && month != Calendar.MAY
            && month != Calendar.JULY && month != Calendar.AUGUST
            && month != Calendar.OCTOBER && month != Calendar.DECEMBER;
      default:
        return date < 0 || date > 31;
      }
    }
    private boolean isLeapYear(int year)
    {
      if (year % 400 == 0)
      {
        return true;
      }
      if (year % 100 == 0)
      {
        return false;
      }
      return year % 4 == 0;
    }
    private int toCalendarMonth(int month, ByteSequence value) throws DecodeException
    {
      switch (month)
      {
      case -1:
        // just allow this.
        return -1;
      case 1:
        return Calendar.JANUARY;
      case 2:
        return Calendar.FEBRUARY;
      case 3:
        return Calendar.MARCH;
      case 4:
        return Calendar.APRIL;
      case 5:
        return Calendar.MAY;
      case 6:
        return Calendar.JUNE;
      case 7:
        return Calendar.JULY;
      case 8:
        return Calendar.AUGUST;
      case 9:
        return Calendar.SEPTEMBER;
      case 10:
        return Calendar.OCTOBER;
      case 11:
        return Calendar.NOVEMBER;
      case 12:
        return Calendar.DECEMBER;
      default:
        LocalizableMessage message = WARN_ATTR_INVALID_MONTH_ASSERTION_FORMAT.get(value, month);
        logger.warn(message);
        throw DecodeException.error(message);
      }
    }
    /** {@inheritDoc} */
    @Override
    public ConditionResult valuesMatch(ByteSequence attributeValue,
        ByteSequence assertionValue)
    {
      // Build the information from the attribute value.
      GregorianCalendar cal = new GregorianCalendar(TIME_ZONE_UTC_OBJ);
      cal.setLenient(false);
      cal.setTimeInMillis(((ByteString) attributeValue).toLong());
      int second = cal.get(Calendar.SECOND);
      int minute = cal.get(Calendar.MINUTE);
      int hour = cal.get(Calendar.HOUR_OF_DAY);
      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 assertSecond = bb.getInt(0);
      int assertMinute = bb.getInt(4);
      int assertHour = bb.getInt(8);
      int assertDate = bb.getInt(12);
      int assertMonth = bb.getInt(16);
      int assertYear = bb.getInt(20);
      // All the non-zero and non -1 values should match.
      if ((assertSecond != -1 && assertSecond != second)
          || (assertMinute != -1 && assertMinute != minute)
          || (assertHour != -1 && assertHour != hour)
          || (assertDate != 0 && assertDate != date)
          || (assertMonth != -1 && assertMonth != month)
          || (assertYear != 0 && assertYear != year))
      {
        return ConditionResult.FALSE;
      }
     return ConditionResult.TRUE;
    }
    /** {@inheritDoc} */
    @Override
    public Collection<ExtensibleIndexer> getIndexers()
    {
      if(indexer == null)
      {
        indexer = new PartialDateAndTimeExtensibleIndexer(this);
      }
      return Collections.singletonList(indexer);
    }
    /** {@inheritDoc} */
    @Override
    public <T> T createIndexQuery(ByteSequence assertionValue,
            IndexQueryFactory<T> factory) throws DecodeException
    {
      //Build the information from the assertion value.
      byte[] arr = normalizeAssertionValue(assertionValue).toByteArray();
      ByteBuffer bb = ByteBuffer.wrap(arr);
      int assertSecond = bb.getInt(0);
      int assertMinute = bb.getInt(4);
      int assertHour = bb.getInt(8);
      int assertDate = bb.getInt(12);
      int assertMonth = bb.getInt(16);
      int assertYear = bb.getInt(20);
      List<T> queries = new ArrayList<T>();
      if(assertSecond >= 0)
      {
        queries.add(createExactMatchQuery(factory, assertSecond, SECOND));
      }
      if(assertMinute >=0)
      {
        queries.add(createExactMatchQuery(factory, assertMinute, MINUTE));
      }
      if(assertHour >=0)
      {
        queries.add(createExactMatchQuery(factory, assertHour, HOUR));
      }
      if(assertDate >0)
      {
        queries.add(createExactMatchQuery(factory, assertDate, DATE));
      }
      if(assertMonth >=0)
      {
        queries.add(createExactMatchQuery(factory, assertMonth, MONTH));
      }
      if(assertYear > 0)
      {
        queries.add(createExactMatchQuery(factory, assertYear, YEAR));
      }
      return factory.createIntersectionQuery(queries);
    }
    private <T> T createExactMatchQuery(IndexQueryFactory<T> factory,
        int assertionValue, char type)
    {
      return factory.createExactMatchQuery(
          indexer.getExtensibleIndexID(), getKey(assertionValue, type));
    }
    /**
     * 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(ByteSequence attributeValue, Collection<ByteString> 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);
      addKeyIfNotZero(keys, cal, Calendar.SECOND, SECOND);
      addKeyIfNotZero(keys, cal, Calendar.MINUTE, MINUTE);
      addKeyIfNotZero(keys, cal, Calendar.HOUR_OF_DAY, HOUR);
      addKeyIfNotZero(keys, cal, Calendar.DATE, DATE);
      addKeyIfNotZero(keys, cal, Calendar.MONTH, MONTH);
      addKeyIfNotZero(keys, cal, Calendar.YEAR, YEAR);
    }
    private void addKeyIfNotZero(Collection<ByteString> keys,
        GregorianCalendar cal, int calField, char type)
    {
      int value = cal.get(calField);
      if (value >= 0)
      {
        keys.add(getKey(value, type));
      }
    }
    private ByteString getKey(int value, char type)
    {
      ByteStringBuilder builder = new ByteStringBuilder();
      builder.append(type);
      builder.append(value);
      return builder.toByteString();
    }
  }
  private int multiplyByTenThenAddUnits(int number, byte b)
  {
    switch (b)
    {
    case '0':
      return number * 10;
    case '1':
      return number * 10 + 1;
    case '2':
      return number * 10 + 2;
    case '3':
      return number * 10 + 3;
    case '4':
      return number * 10 + 4;
    case '5':
      return number * 10 + 5;
    case '6':
      return number * 10 + 6;
    case '7':
      return number * 10 + 7;
    case '8':
      return number * 10 + 8;
    case '9':
      return number * 10 + 9;
    }
    return number;
  }
   /**
   * 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 createKeys(Schema schema, ByteSequence value,
        IndexingOptions options, Collection<ByteString> keys)
    {
      matchingRule.timeKeys(value, keys);
    }
    /** {@inheritDoc} */
    @Override
    public String getIndexID()
    {
      return PARTIAL_DATE_TIME_INDEX_NAME + "." + getExtensibleIndexID();
    }
    /** {@inheritDoc} */
    @Override
    public String getExtensibleIndexID()
    {
      return EXTENSIBLE_INDEXER_ID_DEFAULT;
    }
  }
}